@@ -1471,6 +1471,234 @@ jobs:
14711471 assert n > 0, 'no Translation_Tests UPDATE expectations matched'
14721472 open(path, 'w').write(new_src)
14731473 print(f'patched {n} Translation_Tests UPDATE expectations to EXISTS form')
1474+
1475+ # 17. Turso evaluates correlated scalar subqueries in derived-table
1476+ # SELECT lists incorrectly: the outer alias binding is not
1477+ # refreshed per row, so a WHERE filter on the aliased column
1478+ # mismatches and the prior outer row's value leaks into the
1479+ # next row. The driver computes AUTO_INCREMENT for
1480+ # information_schema.tables / SHOW TABLE STATUS via such a
1481+ # correlated subquery, breaking 4 metadata tests. Replace it
1482+ # with an equivalent JOIN form (LEFT JOIN against a derived
1483+ # table marking auto_increment rows + sqlite_sequence) and a
1484+ # CASE expression at the projection.
1485+ path = 'src/sqlite/class-wp-pdo-mysql-on-sqlite.php'
1486+ src = open(path).read()
1487+
1488+ # 17a. SHOW TABLE STATUS path.
1489+ old_a = (
1490+ "\t\t// Compose a subquery to compute auto-increment values.\n"
1491+ "\t\t$has_sequence_table = (bool) $this->execute_sqlite_query(\n"
1492+ "\t\t\t\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'\"\n"
1493+ "\t\t)->fetchColumn();\n"
1494+ "\n"
1495+ "\t\t$auto_increment_subquery = sprintf(\n"
1496+ "\t\t\t\"(\n"
1497+ "\t\t\t\tSELECT COALESCE(s.seq + 1, 1)\n"
1498+ "\t\t\t\tFROM %s AS c\n"
1499+ "\t\t\t\t%s\n"
1500+ "\t\t\t\tWHERE c.extra = 'auto_increment'\n"
1501+ "\t\t\t\tAND c.table_schema = t.table_schema\n"
1502+ "\t\t\t\tAND c.table_name = t.table_name\n"
1503+ "\t\t\t)\",\n"
1504+ "\t\t\t$this->quote_sqlite_identifier( $columns_table ),\n"
1505+ "\t\t\t$has_sequence_table\n"
1506+ "\t\t\t\t? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name'\n"
1507+ "\t\t\t\t: 'LEFT JOIN (SELECT 0 AS seq) AS s'\n"
1508+ "\t\t);\n"
1509+ "\n"
1510+ "\t\t$query = sprintf(\n"
1511+ "\t\t\t'SELECT * FROM (\n"
1512+ "\t\t\t\tSELECT\n"
1513+ "\t\t\t\t\ttable_name AS `Name`,\n"
1514+ "\t\t\t\t\tengine AS `Engine`,\n"
1515+ "\t\t\t\t\tversion AS `Version`,\n"
1516+ "\t\t\t\t\trow_format AS `Row_format`,\n"
1517+ "\t\t\t\t\ttable_rows AS `Rows`,\n"
1518+ "\t\t\t\t\tavg_row_length AS `Avg_row_length`,\n"
1519+ "\t\t\t\t\tdata_length AS `Data_length`,\n"
1520+ "\t\t\t\t\tmax_data_length AS `Max_data_length`,\n"
1521+ "\t\t\t\t\tindex_length AS `Index_length`,\n"
1522+ "\t\t\t\t\tdata_free AS `Data_free`,\n"
1523+ "\t\t\t\t\t%s AS `Auto_increment`,\n"
1524+ "\t\t\t\t\tcreate_time AS `Create_time`,\n"
1525+ "\t\t\t\t\tupdate_time AS `Update_time`,\n"
1526+ "\t\t\t\t\tcheck_time AS `Check_time`,\n"
1527+ "\t\t\t\t\ttable_collation AS `Collation`,\n"
1528+ "\t\t\t\t\tchecksum AS `Checksum`,\n"
1529+ "\t\t\t\t\tcreate_options AS `Create_options`,\n"
1530+ "\t\t\t\t\ttable_comment AS `Comment`\n"
1531+ "\t\t\t\tFROM %s AS t\n"
1532+ "\t\t\t\tWHERE table_schema = ?\n"
1533+ "\t\t\t)\n"
1534+ "\t\t\tWHERE 1 %s\n"
1535+ "\t\t\tORDER BY `Name`',\n"
1536+ "\t\t\t$auto_increment_subquery,\n"
1537+ "\t\t\t$this->quote_sqlite_identifier( $tables_table ),\n"
1538+ "\t\t\t$condition ?? ''\n"
1539+ "\t\t);\n"
1540+ )
1541+ new_a = (
1542+ "\t\t// JOIN-based AUTO_INCREMENT computation (Turso mis-evaluates\n"
1543+ "\t\t// correlated scalar subqueries in derived-table SELECT lists).\n"
1544+ "\t\t$has_sequence_table = (bool) $this->execute_sqlite_query(\n"
1545+ "\t\t\t\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'\"\n"
1546+ "\t\t)->fetchColumn();\n"
1547+ "\n"
1548+ "\t\t$auto_increment_join = sprintf(\n"
1549+ "\t\t\t\" LEFT JOIN (\n"
1550+ "\t\t\t\tSELECT table_schema AS ai_schema, table_name AS ai_name, 1 AS has_ai\n"
1551+ "\t\t\t\tFROM %s\n"
1552+ "\t\t\t\tWHERE extra = 'auto_increment'\n"
1553+ "\t\t\t\tGROUP BY table_schema, table_name\n"
1554+ "\t\t\t) AS ai ON ai.ai_schema = t.table_schema AND ai.ai_name = t.table_name\",\n"
1555+ "\t\t\t$this->quote_sqlite_identifier( $columns_table )\n"
1556+ "\t\t);\n"
1557+ "\t\tif ( $has_sequence_table ) {\n"
1558+ "\t\t\t$auto_increment_join .= ' LEFT JOIN main.sqlite_sequence AS s ON s.name = t.table_name';\n"
1559+ "\t\t\t$auto_increment_expr = 'CASE WHEN ai.has_ai = 1 THEN COALESCE(s.seq + 1, 1) ELSE NULL END';\n"
1560+ "\t\t} else {\n"
1561+ "\t\t\t$auto_increment_expr = 'CASE WHEN ai.has_ai = 1 THEN 1 ELSE NULL END';\n"
1562+ "\t\t}\n"
1563+ "\n"
1564+ "\t\t$query = sprintf(\n"
1565+ "\t\t\t'SELECT * FROM (\n"
1566+ "\t\t\t\tSELECT\n"
1567+ "\t\t\t\t\ttable_name AS `Name`,\n"
1568+ "\t\t\t\t\tengine AS `Engine`,\n"
1569+ "\t\t\t\t\tversion AS `Version`,\n"
1570+ "\t\t\t\t\trow_format AS `Row_format`,\n"
1571+ "\t\t\t\t\ttable_rows AS `Rows`,\n"
1572+ "\t\t\t\t\tavg_row_length AS `Avg_row_length`,\n"
1573+ "\t\t\t\t\tdata_length AS `Data_length`,\n"
1574+ "\t\t\t\t\tmax_data_length AS `Max_data_length`,\n"
1575+ "\t\t\t\t\tindex_length AS `Index_length`,\n"
1576+ "\t\t\t\t\tdata_free AS `Data_free`,\n"
1577+ "\t\t\t\t\t%s AS `Auto_increment`,\n"
1578+ "\t\t\t\t\tcreate_time AS `Create_time`,\n"
1579+ "\t\t\t\t\tupdate_time AS `Update_time`,\n"
1580+ "\t\t\t\t\tcheck_time AS `Check_time`,\n"
1581+ "\t\t\t\t\ttable_collation AS `Collation`,\n"
1582+ "\t\t\t\t\tchecksum AS `Checksum`,\n"
1583+ "\t\t\t\t\tcreate_options AS `Create_options`,\n"
1584+ "\t\t\t\t\ttable_comment AS `Comment`\n"
1585+ "\t\t\t\tFROM %s AS t%s\n"
1586+ "\t\t\t\tWHERE table_schema = ?\n"
1587+ "\t\t\t)\n"
1588+ "\t\t\tWHERE 1 %s\n"
1589+ "\t\t\tORDER BY `Name`',\n"
1590+ "\t\t\t$auto_increment_expr,\n"
1591+ "\t\t\t$this->quote_sqlite_identifier( $tables_table ),\n"
1592+ "\t\t\t$auto_increment_join,\n"
1593+ "\t\t\t$condition ?? ''\n"
1594+ "\t\t);\n"
1595+ )
1596+ assert old_a in src, 'SHOW TABLE STATUS auto_increment block not found'
1597+ src = src.replace(old_a, new_a, 1)
1598+
1599+ # 17b. translate_table_ref information_schema.tables path.
1600+ # Initialize $auto_increment_join before the per-column loop so
1601+ # the return-time sprintf can append it to the FROM clause.
1602+ old_b1 = (
1603+ "\t\t\t$expanded_list = array();\n"
1604+ "\t\t\tforeach ( $columns as $column ) {\n"
1605+ )
1606+ new_b1 = (
1607+ "\t\t\t$expanded_list = array();\n"
1608+ "\t\t\t$auto_increment_join = '';\n"
1609+ "\t\t\tforeach ( $columns as $column ) {\n"
1610+ )
1611+ assert old_b1 in src, 'translate_table_ref expanded_list init not found'
1612+ src = src.replace(old_b1, new_b1, 1)
1613+
1614+ # Replace the elseif body that builds the auto_increment subquery.
1615+ old_b2 = (
1616+ "\t\t\t\t} elseif ( 'tables' === $table_name && 'AUTO_INCREMENT' === $column ) {\n"
1617+ "\t\t\t\t\t// Inject the auto-increment values.\n"
1618+ "\t\t\t\t\t$columns_table = $this->information_schema_builder->get_table_name( false, 'columns' );\n"
1619+ "\t\t\t\t\t$has_sequence_table = (bool) $this->execute_sqlite_query(\n"
1620+ "\t\t\t\t\t\t\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'\"\n"
1621+ "\t\t\t\t\t)->fetchColumn();\n"
1622+ "\n"
1623+ "\t\t\t\t\t$auto_increment_subquery = sprintf(\n"
1624+ "\t\t\t\t\t\t\"(\n"
1625+ "\t\t\t\t\t\t\tSELECT COALESCE(s.seq + 1, 1)\n"
1626+ "\t\t\t\t\t\t\tFROM %s AS c\n"
1627+ "\t\t\t\t\t\t\t%s\n"
1628+ "\t\t\t\t\t\t\tWHERE c.extra = 'auto_increment'\n"
1629+ "\t\t\t\t\t\t\tAND c.table_schema = %s.table_schema\n"
1630+ "\t\t\t\t\t\t\tAND c.table_name = %s.table_name\n"
1631+ "\t\t\t\t\t\t)\",\n"
1632+ "\t\t\t\t\t\t$this->quote_sqlite_identifier( $columns_table ),\n"
1633+ "\t\t\t\t\t\t$has_sequence_table\n"
1634+ "\t\t\t\t\t\t\t? 'LEFT JOIN main.sqlite_sequence AS s ON s.name = c.table_name'\n"
1635+ "\t\t\t\t\t\t\t: 'LEFT JOIN (SELECT 0 AS seq) AS s',\n"
1636+ "\t\t\t\t\t\t$this->quote_sqlite_identifier( $table_name ),\n"
1637+ "\t\t\t\t\t\t$this->quote_sqlite_identifier( $table_name )\n"
1638+ "\t\t\t\t\t);\n"
1639+ "\n"
1640+ "\t\t\t\t\t$expanded_list[] = sprintf( '%s AS %s', $auto_increment_subquery, $quoted_column );\n"
1641+ )
1642+ new_b2 = (
1643+ "\t\t\t\t} elseif ( 'tables' === $table_name && 'AUTO_INCREMENT' === $column ) {\n"
1644+ "\t\t\t\t\t// JOIN-based AUTO_INCREMENT (Turso mis-evaluates correlated form).\n"
1645+ "\t\t\t\t\t$columns_table = $this->information_schema_builder->get_table_name( false, 'columns' );\n"
1646+ "\t\t\t\t\t$has_sequence_table = (bool) $this->execute_sqlite_query(\n"
1647+ "\t\t\t\t\t\t\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'\"\n"
1648+ "\t\t\t\t\t)->fetchColumn();\n"
1649+ "\n"
1650+ "\t\t\t\t\t$tables_alias = $this->quote_sqlite_identifier( $table_name );\n"
1651+ "\t\t\t\t\t$auto_increment_join = sprintf(\n"
1652+ "\t\t\t\t\t\t\" LEFT JOIN (\n"
1653+ "\t\t\t\t\t\t\tSELECT table_schema AS ai_schema, table_name AS ai_name, 1 AS has_ai\n"
1654+ "\t\t\t\t\t\t\tFROM %s\n"
1655+ "\t\t\t\t\t\t\tWHERE extra = 'auto_increment'\n"
1656+ "\t\t\t\t\t\t\tGROUP BY table_schema, table_name\n"
1657+ "\t\t\t\t\t\t) AS ai ON ai.ai_schema = %s.table_schema AND ai.ai_name = %s.table_name\",\n"
1658+ "\t\t\t\t\t\t$this->quote_sqlite_identifier( $columns_table ),\n"
1659+ "\t\t\t\t\t\t$tables_alias,\n"
1660+ "\t\t\t\t\t\t$tables_alias\n"
1661+ "\t\t\t\t\t);\n"
1662+ "\t\t\t\t\tif ( $has_sequence_table ) {\n"
1663+ "\t\t\t\t\t\t$auto_increment_join .= sprintf(\n"
1664+ "\t\t\t\t\t\t\t' LEFT JOIN main.sqlite_sequence AS s ON s.name = %s.table_name',\n"
1665+ "\t\t\t\t\t\t\t$tables_alias\n"
1666+ "\t\t\t\t\t\t);\n"
1667+ "\t\t\t\t\t\t$auto_increment_expr = 'CASE WHEN ai.has_ai = 1 THEN COALESCE(s.seq + 1, 1) ELSE NULL END';\n"
1668+ "\t\t\t\t\t} else {\n"
1669+ "\t\t\t\t\t\t$auto_increment_expr = 'CASE WHEN ai.has_ai = 1 THEN 1 ELSE NULL END';\n"
1670+ "\t\t\t\t\t}\n"
1671+ "\n"
1672+ "\t\t\t\t\t$expanded_list[] = sprintf( '%s AS %s', $auto_increment_expr, $quoted_column );\n"
1673+ )
1674+ assert old_b2 in src, 'translate_table_ref AUTO_INCREMENT elseif not found'
1675+ src = src.replace(old_b2, new_b2, 1)
1676+
1677+ # Append the JOIN clause to the outer FROM in the return sprintf.
1678+ old_b3 = (
1679+ "\t\t\t// Compose information schema subquery.\n"
1680+ "\t\t\treturn sprintf(\n"
1681+ "\t\t\t\t'(SELECT %s FROM %s AS %s)',\n"
1682+ "\t\t\t\t$column_list,\n"
1683+ "\t\t\t\t$this->quote_sqlite_identifier( $sqlite_table_name ),\n"
1684+ "\t\t\t\t$this->quote_sqlite_identifier( $table_name )\n"
1685+ "\t\t\t);\n"
1686+ )
1687+ new_b3 = (
1688+ "\t\t\t// Compose information schema subquery.\n"
1689+ "\t\t\treturn sprintf(\n"
1690+ "\t\t\t\t'(SELECT %s FROM %s AS %s%s)',\n"
1691+ "\t\t\t\t$column_list,\n"
1692+ "\t\t\t\t$this->quote_sqlite_identifier( $sqlite_table_name ),\n"
1693+ "\t\t\t\t$this->quote_sqlite_identifier( $table_name ),\n"
1694+ "\t\t\t\t$auto_increment_join\n"
1695+ "\t\t\t);\n"
1696+ )
1697+ assert old_b3 in src, 'translate_table_ref return sprintf not found'
1698+ src = src.replace(old_b3, new_b3, 1)
1699+
1700+ open(path, 'w').write(src)
1701+ print('patched AUTO_INCREMENT correlated-subquery -> JOIN form (SHOW TABLE STATUS + information_schema.tables)')
14741702 PY
14751703
14761704 - name : Run PHPUnit tests against Turso DB
0 commit comments