@@ -119,6 +119,113 @@ jobs:
119119 exit 1
120120 fi
121121
122+ - name : Probe Doltlite write-visibility on info-schema-shaped tables
123+ # The ALTER TABLE failures look like "the last DELETE/UPDATE against
124+ # info_schema.columns isn't visible to the follow-up SELECT". Run
125+ # a few targeted scenarios to narrow where the bug is.
126+ run : |
127+ php -r '
128+ $db = new PDO("sqlite::memory:");
129+ $db->exec("PRAGMA foreign_keys = ON");
130+
131+ function probe(PDO $db, string $label, string $setup, string $check, $expected): void {
132+ $db->exec($setup);
133+ $actual = $db->query($check)->fetchAll(PDO::FETCH_NUM);
134+ $ok = json_encode($actual) === json_encode($expected);
135+ printf("[%s] %s\n", $ok ? "PASS" : "FAIL", $label);
136+ if (!$ok) {
137+ printf(" setup: %s\n", str_replace("\n", " ", $setup));
138+ printf(" check: %s\n", $check);
139+ printf(" expected: %s\n", json_encode($expected));
140+ printf(" actual: %s\n", json_encode($actual));
141+ }
142+ }
143+
144+ // 1. Plain rowid table, two sequential DELETEs.
145+ probe($db, "two DELETEs on rowid table",
146+ "DROP TABLE IF EXISTS t1;
147+ CREATE TABLE t1 (ts TEXT, tn TEXT, cn TEXT);
148+ INSERT INTO t1 VALUES (\"s\",\"t\",\"a\"),(\"s\",\"t\",\"b\"),(\"s\",\"t\",\"c\");
149+ DELETE FROM t1 WHERE ts=\"s\" AND tn=\"t\" AND cn=\"a\";
150+ DELETE FROM t1 WHERE ts=\"s\" AND tn=\"t\" AND cn=\"b\";",
151+ "SELECT cn FROM t1 ORDER BY cn",
152+ [["c"]]
153+ );
154+
155+ // 2. Same, but with the composite PK shape info_schema uses.
156+ // (After our ROWID patch, this should be a rowid table + unique index.)
157+ probe($db, "two DELETEs on composite-PK table (rowid-patched)",
158+ "DROP TABLE IF EXISTS t2;
159+ CREATE TABLE t2 (ts TEXT, tn TEXT, cn TEXT, PRIMARY KEY (ts, tn, cn));
160+ INSERT INTO t2 VALUES (\"s\",\"t\",\"a\"),(\"s\",\"t\",\"b\"),(\"s\",\"t\",\"c\");
161+ DELETE FROM t2 WHERE ts=\"s\" AND tn=\"t\" AND cn=\"a\";
162+ DELETE FROM t2 WHERE ts=\"s\" AND tn=\"t\" AND cn=\"b\";",
163+ "SELECT cn FROM t2 ORDER BY cn",
164+ [["c"]]
165+ );
166+
167+ // 3. Same, with prepared statements reused across the two DELETEs
168+ // (same shape the driver uses).
169+ probe($db, "two prepared DELETEs on composite-PK table",
170+ "DROP TABLE IF EXISTS t3;
171+ CREATE TABLE t3 (ts TEXT, tn TEXT, cn TEXT, PRIMARY KEY (ts, tn, cn));
172+ INSERT INTO t3 VALUES (\"s\",\"t\",\"a\"),(\"s\",\"t\",\"b\"),(\"s\",\"t\",\"c\");",
173+ "SELECT cn FROM t3 WHERE cn NOT IN (\"a\",\"b\") ORDER BY cn",
174+ [["c"]]
175+ );
176+ $stmt = $db->prepare("DELETE FROM t3 WHERE ts=? AND tn=? AND cn=?");
177+ $stmt->execute(["s","t","a"]);
178+ $stmt->execute(["s","t","b"]);
179+ $after = $db->query("SELECT cn FROM t3 ORDER BY cn")->fetchAll(PDO::FETCH_NUM);
180+ printf("[%s] two prepared DELETEs actual: %s (expect [[\"c\"]])\n",
181+ json_encode($after) === json_encode([["c"]]) ? "PASS" : "FAIL",
182+ json_encode($after));
183+
184+ // 4. INSERT then DELETE a different row, then SELECT.
185+ probe($db, "INSERT then DELETE on composite-PK table",
186+ "DROP TABLE IF EXISTS t4;
187+ CREATE TABLE t4 (ts TEXT, tn TEXT, cn TEXT, PRIMARY KEY (ts, tn, cn));
188+ INSERT INTO t4 VALUES (\"s\",\"t\",\"a\");
189+ INSERT INTO t4 VALUES (\"s\",\"t\",\"b\");
190+ DELETE FROM t4 WHERE ts=\"s\" AND tn=\"t\" AND cn=\"a\";",
191+ "SELECT cn FROM t4 ORDER BY cn",
192+ [["b"]]
193+ );
194+
195+ // 5. NOCASE column: does Doltlite return the stored case or folded?
196+ probe($db, "NOCASE column preserves original case",
197+ "DROP TABLE IF EXISTS t5;
198+ CREATE TABLE t5 (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE);
199+ INSERT INTO t5 VALUES (1, \"Johnny\");",
200+ "SELECT name FROM t5",
201+ [["Johnny"]]
202+ );
203+
204+ // 6. Same NOCASE column, but covered by a unique index (what VARCHAR
205+ // PK columns look like in the driver).
206+ probe($db, "NOCASE column with unique index preserves original case",
207+ "DROP TABLE IF EXISTS t6;
208+ CREATE TABLE t6 (id INTEGER PRIMARY KEY, name TEXT COLLATE NOCASE, UNIQUE(name));
209+ INSERT INTO t6 VALUES (1, \"Johnny\");",
210+ "SELECT name FROM t6",
211+ [["Johnny"]]
212+ );
213+
214+ // 7. Rebuild via INSERT...SELECT, similar to the ALTER TABLE
215+ // rewrite path; does the case survive the round-trip?
216+ probe($db, "rebuild via INSERT..SELECT preserves case",
217+ "DROP TABLE IF EXISTS t7_old;
218+ DROP TABLE IF EXISTS t7_new;
219+ CREATE TABLE t7_old (ID INTEGER NOT NULL, name TEXT COLLATE NOCASE NOT NULL, PRIMARY KEY (ID, name));
220+ INSERT INTO t7_old VALUES (1, \"Johnny\");
221+ CREATE TABLE t7_new (ID INTEGER NOT NULL, firstname TEXT COLLATE NOCASE, PRIMARY KEY (ID, firstname));
222+ INSERT INTO t7_new (ID, firstname) SELECT ID, name FROM t7_old;",
223+ "SELECT firstname FROM t7_new",
224+ [["Johnny"]]
225+ );
226+ '
227+ continue-on-error : true
228+
122229 - name : Install Composer dependencies (root)
123230 uses : ramsey/composer-install@v3
124231 with :
0 commit comments