Skip to content

SQLiteCloudRowset renames duplicate column names backwards, and columnsNames doesn't match the row keys #282

Description

@damlayildiz

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.columnsNames returns ["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:

  1. 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.
  2. Make columnsNames return the same names the rows are actually keyed by, so the two can be used together.
  3. 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions