Skip to content

Commit a2661f9

Browse files
committed
Merge branch 'develop'
2 parents 3c5455a + 91008f0 commit a2661f9

File tree

4 files changed

+252
-16
lines changed

4 files changed

+252
-16
lines changed

load.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: SQLite Database Integration
44
* Description: SQLite database driver drop-in.
55
* Author: The WordPress Team
6-
* Version: 2.1.14
6+
* Version: 2.1.15
77
* Requires PHP: 7.0
88
* Textdomain: sqlite-database-integration
99
*

readme.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Contributors: wordpressdotorg, aristath
44
Requires at least: 6.4
55
Tested up to: 6.6.1
66
Requires PHP: 7.0
7-
Stable tag: 2.1.14
7+
Stable tag: 2.1.15
88
License: GPLv2 or later
99
License URI: https://www.gnu.org/licenses/gpl-2.0.html
1010
Tags: performance, database

tests/WP_SQLite_Translator_Tests.php

Lines changed: 225 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,14 +1102,14 @@ public function testColumnWithOnUpdate() {
11021102
'name' => '___tmp_table_created_at_on_update__',
11031103
'tbl_name' => '_tmp_table',
11041104
'rootpage' => '0',
1105-
'sql' => "CREATE TRIGGER \"___tmp_table_created_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"created_at\" = CURRENT_TIMESTAMP WHERE id = NEW.id;\n\t\t\tEND",
1105+
'sql' => "CREATE TRIGGER \"___tmp_table_created_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"created_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND",
11061106
),
11071107
(object) array(
11081108
'type' => 'trigger',
11091109
'name' => '___tmp_table_updated_at_on_update__',
11101110
'tbl_name' => '_tmp_table',
11111111
'rootpage' => '0',
1112-
'sql' => "CREATE TRIGGER \"___tmp_table_updated_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"updated_at\" = CURRENT_TIMESTAMP WHERE id = NEW.id;\n\t\t\tEND",
1112+
'sql' => "CREATE TRIGGER \"___tmp_table_updated_at_on_update__\"\n\t\t\tAFTER UPDATE ON \"_tmp_table\"\n\t\t\tFOR EACH ROW\n\t\t\tBEGIN\n\t\t\t UPDATE \"_tmp_table\" SET \"updated_at\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;\n\t\t\tEND",
11131113
),
11141114
),
11151115
$results
@@ -1176,6 +1176,150 @@ public function testColumnWithOnUpdate() {
11761176
$this->assertNull( $result[0]->updated_at );
11771177
}
11781178

1179+
public function testColumnWithOnUpdateAndNoIdField() {
1180+
// CREATE TABLE with ON UPDATE
1181+
$this->assertQuery(
1182+
'CREATE TABLE _tmp_table (
1183+
name varchar(20) NOT NULL,
1184+
created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP
1185+
);'
1186+
);
1187+
1188+
// on INSERT, no timestamps are expected
1189+
$this->assertQuery( "INSERT INTO _tmp_table (name) VALUES ('aaa')" );
1190+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name = 'aaa'" );
1191+
$this->assertNull( $result[0]->created_at );
1192+
1193+
// on UPDATE, we expect timestamps in form YYYY-MM-DD HH:MM:SS
1194+
$this->assertQuery( "UPDATE _tmp_table SET name = 'bbb' WHERE name = 'aaa'" );
1195+
$result = $this->assertQuery( "SELECT * FROM _tmp_table WHERE name = 'bbb'" );
1196+
$this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at );
1197+
}
1198+
1199+
public function testColumnWithOnUpdateAndAutoincrementPrimaryKey() {
1200+
// CREATE TABLE with ON UPDATE, AUTO_INCREMENT, and PRIMARY KEY
1201+
$this->assertQuery(
1202+
'CREATE TABLE _tmp_table (
1203+
id int(11) NOT NULL AUTO_INCREMENT,
1204+
created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP,
1205+
PRIMARY KEY (id)
1206+
);'
1207+
);
1208+
1209+
// on INSERT, no timestamps are expected
1210+
$this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1)' );
1211+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' );
1212+
$this->assertNull( $result[0]->created_at );
1213+
1214+
// on UPDATE, we expect timestamps in form YYYY-MM-DD HH:MM:SS
1215+
$this->assertQuery( 'UPDATE _tmp_table SET id = 2 WHERE id = 1' );
1216+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 2' );
1217+
$this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at );
1218+
}
1219+
1220+
public function testChangeColumnWithOnUpdate() {
1221+
// CREATE TABLE with ON UPDATE
1222+
$this->assertQuery(
1223+
'CREATE TABLE _tmp_table (
1224+
id int(11) NOT NULL,
1225+
created_at timestamp NULL
1226+
);'
1227+
);
1228+
$results = $this->assertQuery( 'DESCRIBE _tmp_table;' );
1229+
$this->assertEquals(
1230+
array(
1231+
(object) array(
1232+
'Field' => 'id',
1233+
'Type' => 'int(11)',
1234+
'Null' => 'NO',
1235+
'Key' => '',
1236+
'Default' => '0',
1237+
'Extra' => '',
1238+
),
1239+
(object) array(
1240+
'Field' => 'created_at',
1241+
'Type' => 'timestamp',
1242+
'Null' => 'YES',
1243+
'Key' => '',
1244+
'Default' => null,
1245+
'Extra' => '',
1246+
),
1247+
),
1248+
$results
1249+
);
1250+
1251+
// no ON UPDATE is set
1252+
$this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (1)' );
1253+
$this->assertQuery( 'UPDATE _tmp_table SET id = 1 WHERE id = 1' );
1254+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' );
1255+
$this->assertNull( $result[0]->created_at );
1256+
1257+
// CHANGE COLUMN to add ON UPDATE
1258+
$this->assertQuery(
1259+
'ALTER TABLE _tmp_table CHANGE COLUMN created_at created_at timestamp NULL ON UPDATE CURRENT_TIMESTAMP'
1260+
);
1261+
$results = $this->assertQuery( 'DESCRIBE _tmp_table;' );
1262+
$this->assertEquals(
1263+
array(
1264+
(object) array(
1265+
'Field' => 'id',
1266+
'Type' => 'int(11)',
1267+
'Null' => 'NO',
1268+
'Key' => '',
1269+
'Default' => '0',
1270+
'Extra' => '',
1271+
),
1272+
(object) array(
1273+
'Field' => 'created_at',
1274+
'Type' => 'timestamp',
1275+
'Null' => 'YES',
1276+
'Key' => '',
1277+
'Default' => null,
1278+
'Extra' => '',
1279+
),
1280+
),
1281+
$results
1282+
);
1283+
1284+
// now, ON UPDATE SHOULD BE SET
1285+
$this->assertQuery( 'UPDATE _tmp_table SET id = 1 WHERE id = 1' );
1286+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 1' );
1287+
$this->assertRegExp( '/\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/', $result[0]->created_at );
1288+
1289+
// change column to remove ON UPDATE
1290+
$this->assertQuery(
1291+
'ALTER TABLE _tmp_table CHANGE COLUMN created_at created_at timestamp NULL'
1292+
);
1293+
$results = $this->assertQuery( 'DESCRIBE _tmp_table;' );
1294+
$this->assertEquals(
1295+
array(
1296+
(object) array(
1297+
'Field' => 'id',
1298+
'Type' => 'int(11)',
1299+
'Null' => 'NO',
1300+
'Key' => '',
1301+
'Default' => '0',
1302+
'Extra' => '',
1303+
),
1304+
(object) array(
1305+
'Field' => 'created_at',
1306+
'Type' => 'timestamp',
1307+
'Null' => 'YES',
1308+
'Key' => '',
1309+
'Default' => null,
1310+
'Extra' => '',
1311+
),
1312+
),
1313+
$results
1314+
);
1315+
1316+
// now, no timestamp is expected
1317+
$this->assertQuery( 'INSERT INTO _tmp_table (id) VALUES (2)' );
1318+
$this->assertQuery( 'UPDATE _tmp_table SET id = 2 WHERE id = 2' );
1319+
$result = $this->assertQuery( 'SELECT * FROM _tmp_table WHERE id = 2' );
1320+
$this->assertNull( $result[0]->created_at );
1321+
}
1322+
11791323
public function testAlterTableWithColumnFirstAndAfter() {
11801324
$this->assertQuery(
11811325
"CREATE TABLE _tmp_table (
@@ -3133,6 +3277,85 @@ public function testCurrentTimestamp() {
31333277
$this->assertQuery( 'DELETE FROM _dates WHERE option_value = CURRENT_TIMESTAMP()' );
31343278
}
31353279

3280+
public function testGroupByHaving() {
3281+
$this->assertQuery(
3282+
'CREATE TABLE _tmp_table (
3283+
name varchar(20)
3284+
);'
3285+
);
3286+
3287+
$this->assertQuery(
3288+
"INSERT INTO _tmp_table VALUES ('a'), ('b'), ('b'), ('c'), ('c'), ('c')"
3289+
);
3290+
3291+
$result = $this->assertQuery(
3292+
'SELECT name, COUNT(*) as count FROM _tmp_table GROUP BY name HAVING COUNT(*) > 1'
3293+
);
3294+
$this->assertEquals(
3295+
array(
3296+
(object) array(
3297+
'name' => 'b',
3298+
'count' => '2',
3299+
),
3300+
(object) array(
3301+
'name' => 'c',
3302+
'count' => '3',
3303+
),
3304+
),
3305+
$result
3306+
);
3307+
}
3308+
3309+
public function testHavingWithoutGroupBy() {
3310+
$this->assertQuery(
3311+
'CREATE TABLE _tmp_table (
3312+
name varchar(20)
3313+
);'
3314+
);
3315+
3316+
$this->assertQuery(
3317+
"INSERT INTO _tmp_table VALUES ('a'), ('b'), ('b'), ('c'), ('c'), ('c')"
3318+
);
3319+
3320+
// HAVING condition satisfied
3321+
$result = $this->assertQuery(
3322+
"SELECT 'T' FROM _tmp_table HAVING COUNT(*) > 1"
3323+
);
3324+
$this->assertEquals(
3325+
array(
3326+
(object) array(
3327+
':param0' => 'T',
3328+
),
3329+
),
3330+
$result
3331+
);
3332+
3333+
// HAVING condition not satisfied
3334+
$result = $this->assertQuery(
3335+
"SELECT 'T' FROM _tmp_table HAVING COUNT(*) > 100"
3336+
);
3337+
$this->assertEquals(
3338+
array(),
3339+
$result
3340+
);
3341+
3342+
// DISTINCT ... HAVING, where only some results meet the HAVING condition
3343+
$result = $this->assertQuery(
3344+
'SELECT DISTINCT name FROM _tmp_table HAVING COUNT(*) > 1'
3345+
);
3346+
$this->assertEquals(
3347+
array(
3348+
(object) array(
3349+
'name' => 'b',
3350+
),
3351+
(object) array(
3352+
'name' => 'c',
3353+
),
3354+
),
3355+
$result
3356+
);
3357+
}
3358+
31363359
/**
31373360
* @dataProvider mysqlVariablesToTest
31383361
*/

wp-includes/sqlite/class-wp-sqlite-translator.php

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,23 +2659,19 @@ private function capture_group_by( $token ) {
26592659
! $token->matches(
26602660
WP_SQLite_Token::TYPE_KEYWORD,
26612661
WP_SQLite_Token::FLAG_KEYWORD_RESERVED,
2662-
array( 'GROUP' )
2662+
array( 'GROUP BY' )
26632663
)
26642664
) {
26652665
return false;
26662666
}
2667-
$next = $this->rewriter->peek_nth( 2 )->value;
2668-
if ( 'BY' !== strtoupper( $next ?? '' ) ) {
2669-
return false;
2670-
}
26712667

26722668
$this->has_group_by = true;
26732669

26742670
return false;
26752671
}
26762672

26772673
/**
2678-
* Translate WHERE something HAVING something to WHERE something AND something.
2674+
* Translate HAVING without GROUP BY to GROUP BY 1 HAVING.
26792675
*
26802676
* @param WP_SQLite_Token $token The token to translate.
26812677
*
@@ -2694,8 +2690,15 @@ private function translate_ungrouped_having( $token ) {
26942690
if ( $this->has_group_by ) {
26952691
return false;
26962692
}
2697-
$this->rewriter->skip();
2698-
$this->rewriter->add( new WP_SQLite_Token( 'AND', WP_SQLite_Token::TYPE_KEYWORD ) );
2693+
2694+
// GROUP BY is missing, add "GROUP BY 1" before the HAVING clause.
2695+
$having = $this->rewriter->skip();
2696+
$this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_DELIMITER ) );
2697+
$this->rewriter->add( new WP_SQLite_Token( 'GROUP BY', WP_SQLite_Token::TYPE_KEYWORD ) );
2698+
$this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_DELIMITER ) );
2699+
$this->rewriter->add( new WP_SQLite_Token( '1', WP_SQLite_Token::TYPE_NUMBER ) );
2700+
$this->rewriter->add( new WP_SQLite_Token( ' ', WP_SQLite_Token::TYPE_DELIMITER ) );
2701+
$this->rewriter->add( $having );
26992702

27002703
return true;
27012704
}
@@ -3115,6 +3118,10 @@ private function execute_alter() {
31153118
$new_field->mysql_data_type
31163119
);
31173120

3121+
// Drop ON UPDATE trigger by the old column name.
3122+
$on_update_trigger_name = $this->get_column_on_update_current_timestamp_trigger_name( $this->table_name, $from_name );
3123+
$this->execute_sqlite_query( "DROP TRIGGER IF EXISTS \"$on_update_trigger_name\"" );
3124+
31183125
/*
31193126
* In SQLite, there is no direct equivalent to the CHANGE COLUMN
31203127
* statement from MySQL. We need to do a bit of work to emulate it.
@@ -3235,6 +3242,11 @@ private function execute_alter() {
32353242
);
32363243
}
32373244

3245+
// Add the ON UPDATE trigger if needed.
3246+
if ( $new_field->on_update ) {
3247+
$this->add_column_on_update_current_timestamp( $this->table_name, $new_field->name );
3248+
}
3249+
32383250
if ( ',' === $alter_terminator->token ) {
32393251
/*
32403252
* If the terminator was a comma,
@@ -3327,9 +3339,6 @@ private function execute_alter() {
33273339
);
33283340
$this->rewriter->drop_last();
33293341

3330-
$on_update_trigger_name = $this->get_column_on_update_current_timestamp_trigger_name( $this->table_name, $op_subject );
3331-
$this->execute_sqlite_query( "DROP TRIGGER IF EXISTS \"$on_update_trigger_name\"" );
3332-
33333342
$this->execute_sqlite_query(
33343343
$this->rewriter->get_updated_query()
33353344
);
@@ -4394,12 +4403,16 @@ private function generate_index_name( $table, $original_index_name ) {
43944403
*/
43954404
private function add_column_on_update_current_timestamp( $table, $column ) {
43964405
$trigger_name = $this->get_column_on_update_current_timestamp_trigger_name( $table, $column );
4406+
4407+
// The trigger wouldn't work for virtual and "WITHOUT ROWID" tables,
4408+
// but currently that can't happen as we're not creating such tables.
4409+
// See: https://www.sqlite.org/rowidtable.html
43974410
$this->execute_sqlite_query(
43984411
"CREATE TRIGGER \"$trigger_name\"
43994412
AFTER UPDATE ON \"$table\"
44004413
FOR EACH ROW
44014414
BEGIN
4402-
UPDATE \"$table\" SET \"$column\" = CURRENT_TIMESTAMP WHERE id = NEW.id;
4415+
UPDATE \"$table\" SET \"$column\" = CURRENT_TIMESTAMP WHERE rowid = NEW.rowid;
44034416
END"
44044417
);
44054418
}

0 commit comments

Comments
 (0)