Column::allows_empty_string()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
/**
3
 * This file contains only a single class.
4
 *
5
 * @file
6
 * @package Tabulate
7
 */
8
9
namespace WordPress\Tabulate\DB;
10
11
/**
12
 * The column class represents a single column in a single table in the database.
13
 */
14
class Column {
15
16
	/**
17
	 * The table to which this column belongs.
18
	 *
19
	 * @var Table
20
	 */
21
	private $table;
22
23
	/**
24
	 * The name of this column.
25
	 *
26
	 * @var string
27
	 */
28
	private $name;
29
30
	/**
31
	 * The type of this column.
32
	 *
33
	 * @var string
34
	 */
35
	private $type;
36
37
	/**
38
	 * The size, or length, of this column.
39
	 *
40
	 * @var integer
41
	 */
42
	private $size;
43
44
	/**
45
	 * This column's collation.
46
	 *
47
	 * @var string
48
	 */
49
	private $collation;
50
51
	/**
52
	 * The total number of digits in a DECIMAL column.
53
	 *
54
	 * @var integer
55
	 */
56
	private $precision;
57
58
	/**
59
	 * The number of digits after the decimal point in a DECIMAL column.
60
	 *
61
	 * @var integer
62
	 */
63
	private $scale;
64
65
	/**
66
	 * Whether or not this column is the Primary Key.
67
	 *
68
	 * @var boolean
69
	 */
70
	private $is_primary_key = false;
71
72
	/**
73
	 * Whether or not this column is a Unique Key.
74
	 *
75
	 * @var boolean
76
	 */
77
	private $is_unique = false;
78
79
	/**
80
	 * The default value for this column.
81
	 *
82
	 * @var mixed
83
	 */
84
	private $default_value;
85
86
	/**
87
	 * Whether or not this column is auto-incrementing.
88
	 *
89
	 * @var boolean
90
	 */
91
	private $is_auto_increment = false;
92
93
	/**
94
	 * Whether NULL values are allowed for this column.
95
	 *
96
	 * @var boolean
97
	 */
98
	private $nullable;
99
100
	/**
101
	 * Is this an unsigned number?
102
	 *
103
	 * @var boolean
104
	 */
105
	private $unsigned = false;
106
107
	/**
108
	 * ENUM options.
109
	 *
110
	 * @var string[]
111
	 */
112
	private $options;
113
114
	/**
115
	 * The comment attached to this column.
116
	 *
117
	 * @var string
118
	 */
119
	private $comment;
120
121
	/**
122
	 * The table that this column refers to, or false if it is not a foreign key.
123
	 *
124
	 * @var Table|false
125
	 */
126
	private $references = false;
127
128
	/**
129
	 * Create a column of a given table and based on given info.
130
	 *
131
	 * @param \WordPress\Tabulate\DB\Table $table The table that this column belongs to.
132
	 * @param string[]                     $info The output array of a SHOW COLUMNS query.
133
	 */
134
	public function __construct( Table $table, $info = false ) {
135
		$this->table = $table;
136
		$this->parse_info( $info );
0 ignored issues
show
Bug introduced by
It seems like $info defined by parameter $info on line 134 can also be of type false; however, WordPress\Tabulate\DB\Column::parse_info() does only seem to accept array<integer,string>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
137
	}
138
139
	/**
140
	 * Take the output of SHOW COLUMNS and populate this object's data.
141
	 *
142
	 * @param string[] $info The output array of a SHOW COLUMNS query.
143
	 */
144
	protected function parse_info( $info ) {
145
146
		// Name.
147
		$this->name = $info['Field'];
148
149
		// Type.
150
		$this->parse_type( $info['Type'] );
151
152
		// Default.
153
		$this->default_value = $info['Default'];
154
155
		// Primary key.
156
		if ( 'PRI' === strtoupper( $info['Key'] ) ) {
157
			$this->is_primary_key = true;
158
			if ( 'auto_increment' === $info['Extra'] ) {
159
				$this->is_auto_increment = true;
160
			}
161
		}
162
163
		// Unique key.
164
		$this->is_unique = ( 'UNI' === strtoupper( $info['Key'] ) );
165
166
		// Comment.
167
		$this->comment = $info['Comment'];
168
169
		// Collation.
170
		$this->collation = $info['Collation'];
171
172
		// Is this column NULL?
173
		$this->nullable = ( 'YES' === $info['Null'] );
174
175
		// Is this a foreign key?
176
		if ( in_array( $this->get_name(), $this->get_table()->get_foreign_key_names(), true ) ) {
177
			$referenced_tables = $this->get_table()->get_referenced_tables( false );
178
			$this->references = $referenced_tables[ $this->get_name() ];
0 ignored issues
show
Documentation Bug introduced by
It seems like $referenced_tables[$this->get_name()] can also be of type string. However, the property $references is declared as type object<WordPress\Tabulate\DB\Table>|false. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
179
		}
180
181
	}
182
183
	/**
184
	 * Get this column's name.
185
	 *
186
	 * @return string The name of this column.
187
	 */
188
	public function get_name() {
189
		return $this->name;
190
	}
191
192
	/**
193
	 * Get the valid options for this column; only applies to ENUM and SET.
194
	 *
195
	 * @return array The available options.
196
	 */
197
	public function get_options() {
198
		return $this->options;
199
	}
200
201
	/**
202
	 * Get the human-readable title of this column.
203
	 */
204
	public function get_title() {
205
		return \WordPress\Tabulate\Text::titlecase( $this->get_name() );
206
	}
207
208
	/**
209
	 * Get this column's type.
210
	 *
211
	 * @return string The type of this column.
212
	 */
213
	public function get_type() {
214
		return $this->type;
215
	}
216
217
	/**
218
	 * Get the definitive list of xtypes.
219
	 *
220
	 * @return string[] The xtypes.
221
	 */
222
	public static function get_xtypes() {
223
		return array(
224
			'text_short' => array(
225
				'name' => 'text_short',
226
				'title' => 'Text (short)',
227
				'type' => 'VARCHAR',
228
				'sizes' => 1,
229
				'options' => array(),
230
			),
231
			'text_long' => array(
232
				'name' => 'text_long',
233
				'title' => 'Text (long)',
234
				'type' => 'TEXT',
235
				'sizes' => 0,
236
				'options' => array( 'autop', 'html', 'md', 'rst', 'plain' ),
237
			),
238
			'integer' => array(
239
				'name' => 'integer',
240
				'title' => 'Integer',
241
				'type' => 'INT',
242
				'sizes' => 1,
243
				'options' => array(),
244
			),
245
			'boolean' => array(
246
				'name' => 'boolean',
247
				'title' => 'Boolean',
248
				'type' => 'TINYINT',
249
				'sizes' => 0,
250
				'options' => array(),
251
			),
252
			'decimal' => array(
253
				'name' => 'decimal',
254
				'title' => 'Decimal',
255
				'type' => 'DECIMAL',
256
				'sizes' => 2,
257
				'options' => array(),
258
			),
259
			'date' => array(
260
				'name' => 'date',
261
				'title' => 'Date',
262
				'type' => 'DATE',
263
				'sizes' => 0,
264
				'options' => array(),
265
			),
266
			'time' => array(
267
				'name' => 'time',
268
				'title' => 'Time',
269
				'type' => 'TIME',
270
				'sizes' => 0,
271
				'options' => array(),
272
			),
273
			'datetime' => array(
274
				'name' => 'datetime',
275
				'title' => 'Date & Time',
276
				'type' => 'DATETIME',
277
				'sizes' => 0,
278
				'options' => array(),
279
			),
280
			'fk' => array(
281
				'name' => 'fk',
282
				'title' => 'Cross Reference',
283
				'type' => 'INT',
284
				'sizes' => 1,
285
				'options' => array(),
286
			),
287
			'point' => array(
288
				'name' => 'point',
289
				'title' => 'Geographic location',
290
				'type' => 'POINT',
291
				'sizes' => 0,
292
				'options' => array(),
293
			),
294
			'enum' => array(
295
				'name' => 'enum',
296
				'title' => 'Fixed list',
297
				'type' => 'ENUM',
298
				'sizes' => 0,
299
				'options' => array(),
300
			),
301
		);
302
	}
303
304
	/**
305
	 * Get the X-Type of this column.
306
	 *
307
	 * @return string[] An array containing details of the xtype: name, title, type, sizes, and options.
308
	 */
309
	public function get_xtype() {
310
		$xtypes = self::get_xtypes();
311
		if ( $this->is_foreign_key() ) {
312
			return $xtypes['fk'];
313
		}
314
		if ( $this->is_boolean() ) {
315
			return $xtypes['boolean'];
316
		}
317
		// Otherwise fall back on the first xtype with a matching type.
318
		foreach ( $xtypes as $xtype ) {
319
			if ( strtoupper( $this->get_type() ) === $xtype['type'] ) {
320
				return $xtype;
321
			}
322
		}
323
		return false;
324
	}
325
326
	/**
327
	 * Set the X-Type of this column.
328
	 *
329
	 * @param string $type The name of the type.
330
	 */
331
	public function set_xtype( $type ) {
332
		$option_name = TABULATE_SLUG . '_xtypes';
333
		$xtypes = update_option( $option_name );
334
		$table_name = $this->get_table()->get_name();
335
		if ( ! is_array( $xtypes[ $table_name ] ) ) {
336
			$xtypes[ $table_name ] = array();
337
		}
338
		$xtypes[ $table_name ][ $this->get_name() ] = $type;
339
		update_option( $option_name, $xtypes );
340
	}
341
342
	/**
343
	 * Get the column's comment.
344
	 *
345
	 * @return string
346
	 */
347
	public function get_comment() {
348
		return $this->comment;
349
	}
350
351
	/**
352
	 * Get the default value for this column.
353
	 *
354
	 * @return mixed
355
	 */
356
	public function get_default() {
357
		if ( 'CURRENT_TIMESTAMP' === $this->default_value ) {
358
			return date( 'Y-m-d h:i:s' );
359
		}
360
		return $this->default_value;
361
	}
362
363
	/**
364
	 * Get this column's size, or (for ENUM columns) its CSV options string.
365
	 *
366
	 * @return int|string The size of this column.
367
	 */
368
	public function get_size() {
369
		$size = $this->size;
370
		if ( 'decimal' === $this->get_type() ) {
371
			$size = "$this->precision,$this->scale";
372
		}
373
		if ( 'enum' === $this->get_type() ) {
374
			return "'" . join( "','", $this->get_options() ) . "'";
375
		}
376
		return $size;
377
	}
378
379
	/**
380
	 * Whether or not a non-NULL value needs to be supplied for this column.
381
	 *
382
	 * Not-NULL columns that have default values are *not* considered to be
383
	 * required.
384
	 *
385
	 * @return boolean
386
	 */
387
	public function is_required() {
388
		$has_default = ( $this->get_default() !== null || $this->is_auto_increment() );
389
		return ( ! $this->nullable() && ! $has_default );
390
	}
391
392
	/**
393
	 * Whether or not this column is the Primary Key for its table.
394
	 *
395
	 * @return boolean True if this is the PK, false otherwise.
396
	 */
397
	public function is_primary_key() {
398
		return $this->is_primary_key;
399
	}
400
401
	/**
402
	 * Whether or not this column is a unique key.
403
	 *
404
	 * @return boolean True if this is a Unique Key, false otherwise.
405
	 */
406
	public function is_unique() {
407
		return $this->is_unique;
408
	}
409
410
	/**
411
	 * Whether or not this column is an auto-incrementing integer.
412
	 *
413
	 * @return boolean True if this column has AUTO_INCREMENT set, false otherwise.
414
	 */
415
	public function is_auto_increment() {
416
		return $this->is_auto_increment;
417
	}
418
419
	/**
420
	 * Whether or not this column is allowed to have NULL values.
421
	 *
422
	 * @return boolean
423
	 */
424
	public function nullable() {
425
		return $this->nullable;
426
	}
427
428
	/**
429
	 * Only NOT NULL text fields are allowed to have empty strings.
430
	 *
431
	 * @return boolean
432
	 */
433
	public function allows_empty_string() {
434
		$text_types = array( 'text', 'varchar', 'char' );
435
		$is_text_type = in_array( $this->get_type(), $text_types, true );
436
		return ( ! $this->nullable() ) && $is_text_type;
437
	}
438
439
	/**
440
	 * Is this a boolean field?
441
	 *
442
	 * This method deals with the silliness that is MySQL's boolean datatype. Or, rather, it will do when it's finished.
443
	 * For now, it just reports true when this is a TINYINT(1) column.
444
	 *
445
	 * @return boolean
446
	 */
447
	public function is_boolean() {
448
		return $this->get_type() === 'tinyint' && $this->get_size() === 1;
449
	}
450
451
	/**
452
	 * Whether this column is an unsigned number.
453
	 *
454
	 * @return boolean
455
	 */
456
	public function is_unsigned() {
457
		return $this->unsigned;
458
	}
459
460
	/**
461
	 * Whether or not this column is an integer, float, or decimal column.
462
	 */
463
	public function is_numeric() {
464
		$is_int = substr( $this->get_type(), 0, 3 ) === 'int';
465
		$is_decimal = substr( $this->get_type(), 0, 7 ) === 'decimal';
466
		$is_float = substr( $this->get_type(), 0, 5 ) === 'float';
467
		return $is_int || $is_decimal || $is_float;
468
	}
469
470
	/**
471
	 * Whether or not this column is a foreign key.
472
	 *
473
	 * @return boolean True if $this->_references is not empty, otherwise false.
474
	 */
475
	public function is_foreign_key() {
476
		return ! empty( $this->references );
477
	}
478
479
	/**
480
	 * Get the table object of the referenced table, if this column is a foreign
481
	 * key.
482
	 *
483
	 * @return Table The referenced table.
484
	 */
485
	public function get_referenced_table() {
486
		return $this->table->get_database()->get_table( $this->references );
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->table->get_databa...ble($this->references); of type false|WordPress\Tabulate\DB\Table adds false to the return on line 486 which is incompatible with the return type documented by WordPress\Tabulate\DB\Column::get_referenced_table of type WordPress\Tabulate\DB\Table. It seems like you forgot to handle an error condition.
Loading history...
487
	}
488
489
	/**
490
	 * Get the table that this column belongs to.
491
	 *
492
	 * @return Table The table object.
493
	 */
494
	public function get_table() {
495
		return $this->table;
496
	}
497
498
	/**
499
	 * Take an SQL string and parse out column information.
500
	 *
501
	 * @param string $type_string The SQL.
502
	 */
503
	private function parse_type( $type_string ) {
504
505
		$this->unsigned = ( false !== stripos( $type_string, 'unsigned' ) );
506
507
		$varchar_pattern = '/^((?:var)?char)\((\d+)\)/';
508
		$decimal_pattern = '/^decimal\((\d+),(\d+)\)/';
509
		$float_pattern = '/^float\((\d+),(\d+)\)/';
510
		$integer_pattern = '/^((?:big|medium|small|tiny)?int|year)\(?(\d+)\)?/';
511
		$enum_pattern = '/^(enum|set)\(\'(.*?)\'\)/';
512
513
		$this->type = $type_string;
514
		$this->size = null;
515
		$this->precision = null;
516
		$this->scale = null;
517
		$this->options = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array<integer,string> of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
518
		if ( preg_match( $varchar_pattern, $type_string, $matches ) ) {
519
			$this->type = $matches[1];
520
			$this->size = (int) $matches[2];
521
		} elseif ( preg_match( $decimal_pattern, $type_string, $matches ) ) {
522
			$this->type = 'decimal';
523
			$this->precision = $matches[1];
0 ignored issues
show
Documentation Bug introduced by
The property $precision was declared of type integer, but $matches[1] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
524
			$this->scale = $matches[2];
0 ignored issues
show
Documentation Bug introduced by
The property $scale was declared of type integer, but $matches[2] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
525
		} elseif ( preg_match( $float_pattern, $type_string, $matches ) ) {
526
			$this->type = 'float';
527
			$this->precision = $matches[1];
528
			$this->scale = $matches[2];
529
		} elseif ( preg_match( $integer_pattern, $type_string, $matches ) ) {
530
			$this->type = $matches[1];
531
			$this->size = (int) $matches[2];
532
		} elseif ( preg_match( $enum_pattern, $type_string, $matches ) ) {
533
			$this->type = $matches[1];
534
			$values = explode( "','", $matches[2] );
535
			$this->options = array_combine( $values, $values );
536
		}
537
	}
538
539
	/**
540
	 * Get a human-readable string representation of this column.
541
	 *
542
	 * @return string
543
	 */
544
	public function __toString() {
545
		$pk = ($this->is_primary_key) ? ' PK' : '';
546
		$auto = ($this->is_auto_increment) ? ' AI' : '';
547
		if ( $this->references ) {
548
			$ref = ' References ' . $this->references . '.';
549
		} else {
550
			$ref = '';
551
		}
552
		$size = ($this->size > 0) ? "($this->size)" : '';
553
		return $this->name . ' ' . strtoupper( $this->type ) . $size . $pk . $auto . $ref;
554
	}
555
556
	/**
557
	 * Get the defining SQL for this column.
558
	 *
559
	 * @return string
560
	 */
561
	public function get_current_column_definition() {
562
		return self::get_column_definition(
563
			$this->get_name(),
564
			$this->get_xtype()['name'],
565
			$this->get_size(),
566
			$this->nullable(),
567
			$this->get_default(),
568
			$this->is_auto_increment(),
569
			$this->is_unique(),
570
			$this->get_comment(),
571
			$this->get_referenced_table()
572
		);
573
	}
574
575
	/**
576
	 * Alter this column.
577
	 *
578
	 * @param string                       $new_name The column name.
579
	 * @param string                       $xtype_name The x-type name. Must exist.
580
	 * @param string                       $size Either a single integer, or a x,y string of two integers.
581
	 * @param boolean                      $nullable Whether to allow NULl values.
582
	 * @param string                       $default The default value.
583
	 * @param boolean                      $auto_increment Auto-increment or not.
584
	 * @param boolean                      $unique Whether a unique constraint should apply.
585
	 * @param string                       $comment The column's comment. Default empty.
586
	 * @param \WordPress\Tabulate\DB\Table $target_table The target table for a foreign key.
587
	 * @param string                       $after The column that this one will be after.
588
	 * @throws Exception If unable to alter the table.
589
	 */
590
	public function alter( $new_name = null, $xtype_name = null, $size = null, $nullable = null, $default = null, $auto_increment = null, $unique = null, $comment = null, $target_table = null, $after = null ) {
591
		// Any that have not been set explicitly should be unchanged.
592
		$new_name = ! is_null( $new_name ) ? (string) $new_name : $this->get_name();
593
		$xtype_name = ! is_null( $xtype_name ) ? (string) $xtype_name : $this->get_xtype()['name'];
594
		$size = ! is_null( $size ) ? $size : $this->get_size();
595
		$nullable = ! is_null( $nullable ) ? (boolean) $nullable : $this->nullable();
596
		$default = ! is_null( $default ) ? (string) $default : $this->get_default();
597
		$auto_increment = ! is_null( $auto_increment ) ? (boolean) $auto_increment : $this->is_auto_increment();
598
		$unique = ! is_null( $unique ) ? (boolean) $unique : $this->is_unique();
599
		$comment = ! is_null( $comment ) ? (string) $comment : $this->get_comment();
600
		$target_table = ! is_null( $target_table ) ? $target_table : $this->get_referenced_table();
601
602
		// Check the current column definition.
603
		$col_def = self::get_column_definition( $new_name, $xtype_name, $size, $nullable, $default, $auto_increment, $unique, $comment, $target_table, $after );
604
		if ( $this->get_current_column_definition() === $col_def ) {
605
			return;
606
		}
607
608
		// Drop the unique key if it exists; it'll be re-created after.
609
		$table = $this->get_table();
610
		$wpdb = $table->get_database()->get_wpdb();
611
		if ( $this->is_unique() ) {
612
			$sql = 'SHOW INDEXES FROM `' . $table->get_name() . '` WHERE Column_name LIKE "' . $this->get_name() . '"';
613
			foreach ( $wpdb->get_results( $sql, ARRAY_A ) as $index ) {
614
				$sql = "DROP INDEX `" . $index['Key_name'] . "` ON `" . $table->get_name() . "`";
615
				$wpdb->query( $sql );
616
			}
617
		}
618
619
		// Drop any foreign keys if they exist; they'll be re-created after.
620
		if ( $this->is_foreign_key() ) {
621
			$fks_sql = 'SELECT CONSTRAINT_NAME AS fk_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS '
622
				. ' WHERE TABLE_SCHEMA = SCHEMA() '
623
				. ' AND TABLE_NAME = "' . $table->get_name() . '" '
624
				. ' AND CONSTRAINT_TYPE = "FOREIGN KEY" ';
625
			$fks = $wpdb->get_results( $fks_sql );
626
			foreach ( $fks as $key ) {
627
				$sql = 'ALTER TABLE `' . $table->get_name() . '` DROP FOREIGN KEY `' . $key->fk_name . '`';
628
				$wpdb->query( $sql );
629
			}
630
			$this->references = false;
631
		}
632
633
		// Alter the column.
634
		$sql = "ALTER TABLE `" . $table->get_name() . "` CHANGE COLUMN `" . $this->get_name() . "` $col_def";
635
		$wpdb->hide_errors();
636
		$altered = $wpdb->query( $sql );
637
		if ( false === $altered ) {
638
			$err = "Unable to alter '" . $table->get_name() . "." . $this->get_name() . "'"
639
				. " &mdash; $wpdb->last_error &mdash; <code>$sql</code>";
640
			throw new Exception( $err );
641
		}
642
		$wpdb->show_errors();
643
644
		// Reset the Column and Table objects' data.
645
		$table->reset();
646
		$sql = "SHOW FULL COLUMNS FROM `" . $table->get_name() . "` LIKE '$new_name'";
647
		$column_info = $table->get_database()->get_wpdb()->get_row( $sql, ARRAY_A );
648
		$this->parse_info( $column_info );
649
		if ( $this->is_foreign_key() ) {
650
			$this->get_referenced_table()->reset();
651
		}
652
	}
653
654
	/**
655
	 * Get an SQL column definition.
656
	 *
657
	 * @param string                       $name The column name.
658
	 * @param string                       $xtype_name The x-type name. Must exist.
659
	 * @param string                       $size Either a single integer, or a x,y string of two integers.
660
	 * @param boolean                      $nullable Whether to allow NULl values.
661
	 * @param string                       $default The default value.
662
	 * @param boolean                      $auto_increment Auto-increment or not.
663
	 * @param boolean                      $unique Whether a unique constraint should apply.
664
	 * @param string                       $comment The column's comment. Default empty.
665
	 * @param \WordPress\Tabulate\DB\Table $target_table The target table for a foreign key.
666
	 * @param string                       $after The column that this one will be after.
667
	 * @return string
668
	 */
669
	public static function get_column_definition( $name, $xtype_name = null, $size = null, $nullable = true, $default = null, $auto_increment = null, $unique = null, $comment = null, $target_table = null, $after = null ) {
670
		// Type.
671
		$xtypes = self::get_xtypes();
672
		$xtype = ( isset( $xtypes[ $xtype_name ] ) ) ? $xtypes[ $xtype_name ] : $xtypes['text_short'];
673
		$type_str = $xtype['type'];
674
		// Size or options.
675
		$size_str = '';
676
		if ( is_numeric( $xtype['sizes'] ) && $xtype['sizes'] > 0 ) {
677
			$size_str = '(' . ( $size ? : 50 ) . ')';
678
		}
679
		if ( 'enum' === $xtype_name ) {
680
			// If not already wraped in quotes, explode and quote each option.
681
			if ( 0 === preg_match( '/^["\'].*["\']$/', $size ) ) {
682
				$size = "'" . join( "','", explode( ',', $size ) ) . "'";
683
			}
684
			$size_str = "($size)";
685
		}
686
		if ( 'boolean' === $xtype_name ) {
687
			$size_str = '(1)';
688
		}
689
		// Nullable.
690
		$null_str = (true === $nullable) ? 'NULL' : 'NOT NULL';
691
		// Default.
692
		$default_str = '';
693
		if ( 'text_long' !== $xtype_name ) {
694
			$default_str = ! empty( $default ) ? "DEFAULT '$default'" : ( $nullable ? 'DEFAULT NULL' : '' );
695
		}
696
		$auto_increment_str = '';
697
		if ( $auto_increment && 'integer' === $xtype['name'] ) {
698
			$auto_increment_str = 'AUTO_INCREMENT';
699
		}
700
		$unique_str = $unique ? 'UNIQUE' : '';
701
		$comment_str = ! is_null( $comment ) ? "COMMENT '$comment'" : '';
702
703
		$after_str = ( ! empty( $after ) ) ? "AFTER `$after`" : '';
704
		if ( 'FIRST' === strtoupper( $after ) ) {
705
			$after_str = " FIRST ";
706
		}
707
708
		$ref_str = '';
709
		$sign_str = '';
710
		if ( $target_table instanceof \WordPress\Tabulate\DB\Table ) {
711
			$pk_col = $target_table->get_pk_column();
712
			$ref_str = ', ADD CONSTRAINT `' . $name . '_fk_to_' . $target_table->get_name() . '`'
713
				. ' FOREIGN KEY (`' . $name . '`) '
714
				. ' REFERENCES `' . $target_table->get_name() . '` '
715
				. ' (`' . $pk_col->get_name() . '`)';
716
			$type_str = $pk_col->get_type();
717
			$size_str = '(' . $pk_col->get_size() . ')';
718
			$sign_str = ($pk_col->is_unsigned()) ? 'UNSIGNED' : '';
719
		}
720
721
		// Put it all together.
722
		$col_def = "`$name` $type_str$size_str $sign_str $null_str $default_str $auto_increment_str $unique_str $comment_str $after_str $ref_str";
723
		return preg_replace( '/ +/', ' ', trim( $col_def ) );
724
725
	}
726
}
727