Repository navigation
SQLiteCloudRowset renames duplicate column names backwards, and columnsNames doesn't match the row keys #282
Description
A table can't have two columns with the same name, but a query result can — SELECT * over a join is the everyday case:
SELECT a.id, b.id FROM a JOIN b ON b.a_id = a.id; -- id, id
SELECT * FROM orders o JOIN customers c ON …; -- id, id, name, name, …
SELECT count(*), count(*) FROM t; -- count(*), count(*)An object can't hold two id keys, so SQLiteCloudRowset renames duplicates when building each SQLiteCloudRow (lib/drivers/rowset.js, constructor). Two bugs in that renaming:
1. The first duplicate is renamed, the last keeps the clean name — backwards from what callers expect.
Reproduces with no connection:
const { SQLiteCloudRowset } = require("@sqlitecloud/drivers");
const rowset = new SQLiteCloudRowset(
{
version: 1,
numberOfRows: 1,
numberOfColumns: 3,
columns: [{ name: "id" }, { name: "id" }, { name: "id" }],
},
[1, 2, 3],
);
console.log(rowset[0]); // { id_0: 1, id_0_1: 2, id: 3 }
console.log(rowset.columnsNames); // [ 'id', 'id', 'id' ]
console.log(rowset[0].getData()); // [ 1, 2, 3 ]Expected for SELECT a.id, b.id (a.id=7, b.id=42): { id: 7, id_1: 42 }. Actual: { id_0: 7, id: 42 }.
The rename loop walks columns left to right and renames the current column if any other column shares its name. The first id sees a duplicate later in the list and gets renamed; by the time the loop reaches the last id, the earlier ones have already been renamed away, so it looks unique and stays clean. Code that reads row.id almost always means the first id in the select list — the driver hands back the last one instead.
2. columnsNames doesn't match the renamed row keys.
The constructor renames a local copy of the names, but the columnsNames getter maps over metadata.columns fresh on every call — it never sees the rename. So:
- rows have the keys
id_0,id_0_1,id(see repro above) rowset.columnsNamesreturns["id", "id", "id"]
Any consumer that builds headers from columnsNames and reads cells with row[name] reads row["id"] for every duplicate column and silently gets the same (last) value in every cell — no error, no empty cell, nothing to suggest the data is wrong.
Impact example: customers(id=7, name="Ada") joined with orders(id=42, customer_id=7, name="Laptop"):
SELECT * FROM customers c JOIN orders o ON o.customer_id = c.id;A naive consumer using columnsNames + row[name] renders id=42, name="Laptop" for both the customer and order columns — the customer's own id/name are silently replaced by the order's, with correct-looking headers, column count, and row count.
Requested fix:
- Suffix the later duplicates, keep the first occurrence's name clean:
id,id_1,id_2. Skip any suffix already used by a real column. - Make
columnsNamesreturn the same names the rows are actually keyed by, so the two can be used together. - Expose the original, un-renamed names separately (e.g.
originalColumnsNames) for consumers that want to show exactly what the query returned.
Note: fixing #1 changes the keys of any row that contains duplicate names today, so it's a breaking change for anyone relying on the current id_0/id_0_1 scheme — worth a changelog note, though those keys seem unlikely to be depended on deliberately.
Tested against @sqlitecloud/drivers 1.0.878.
Activity