Skip to content

Commit

Permalink
feat(store-sync): snake case postgres names in decoded tables (#1989)
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Dec 1, 2023
1 parent 59d78c9 commit 7b73f44
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 20 deletions.
7 changes: 7 additions & 0 deletions .changeset/young-pandas-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@latticexyz/store-sync": major
---

Postgres storage adapter now uses snake case for decoded table names and column names. This allows for better SQL ergonomics when querying these tables.

To avoid naming conflicts for now, schemas are still case-sensitive and need to be queried with double quotes. We may change this in the future with [namespace validation](https://github.com/latticexyz/mud/issues/1991).
1 change: 1 addition & 0 deletions packages/store-sync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@latticexyz/world": "workspace:*",
"@trpc/client": "10.34.0",
"@trpc/server": "10.34.0",
"change-case": "^5.2.0",
"debug": "^4.3.4",
"drizzle-orm": "^0.28.5",
"kysely": "^0.26.3",
Expand Down
30 changes: 15 additions & 15 deletions packages/store-sync/src/postgres-decoded/buildTable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ describe("buildTable", () => {
it("should create table from schema", async () => {
const table = buildTable({
address: "0xffffffffffffffffffffffffffffffffffffffff",
namespace: "test",
name: "users",
namespace: "testNS",
name: "UsersTable",
keySchema: { x: "uint32", y: "uint32" },
valueSchema: { name: "string", addr: "address" },
valueSchema: { name: "string", walletAddress: "address" },
});

expect(getTableConfig(table).schema).toMatch(/^test_\d+__0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test$/);
expect(getTableConfig(table).name).toMatchInlineSnapshot('"users"');
expect(getTableConfig(table).schema).toMatch(/0xffffffffffffffffffffffffffffffffffffffff__testNS$/);
expect(getTableConfig(table).name).toMatchInlineSnapshot('"users_table"');
expect(
mapObject(getTableColumns(table), (column) => ({
name: column.name,
Expand All @@ -37,18 +37,18 @@ describe("buildTable", () => {
"notNull": false,
"sqlName": "numeric",
},
"addr": {
"dataType": "custom",
"name": "addr",
"notNull": true,
"sqlName": "bytea",
},
"name": {
"dataType": "string",
"name": "name",
"notNull": true,
"sqlName": undefined,
},
"walletAddress": {
"dataType": "custom",
"name": "wallet_address",
"notNull": true,
"sqlName": "bytea",
},
"x": {
"dataType": "custom",
"name": "x",
Expand All @@ -68,14 +68,14 @@ describe("buildTable", () => {
it("can create a singleton table", async () => {
const table = buildTable({
address: "0xffffffffffffffffffffffffffffffffffffffff",
namespace: "test",
name: "users",
namespace: "testNS",
name: "UsersTable",
keySchema: {},
valueSchema: { addrs: "address[]" },
});

expect(getTableConfig(table).schema).toMatch(/^test_\d+__0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF__test$/);
expect(getTableConfig(table).name).toMatchInlineSnapshot('"users"');
expect(getTableConfig(table).schema).toMatch(/0xffffffffffffffffffffffffffffffffffffffff__testNS$/);
expect(getTableConfig(table).name).toMatchInlineSnapshot('"users_table"');
expect(
mapObject(getTableColumns(table), (column) => ({
name: column.name,
Expand Down
16 changes: 11 additions & 5 deletions packages/store-sync/src/postgres-decoded/buildTable.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PgColumnBuilderBase, PgTableWithColumns, pgSchema } from "drizzle-orm/pg-core";
import { Address, getAddress } from "viem";
import { snakeCase } from "change-case";
import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser";
import { asBigInt, asHex } from "../postgres/columnTypes";
import { transformSchemaName } from "../postgres/transformSchemaName";
Expand Down Expand Up @@ -46,14 +47,19 @@ export function buildTable<TKeySchema extends KeySchema, TValueSchema extends Va
keySchema,
valueSchema,
}: BuildTableOptions<TKeySchema, TValueSchema>): BuildTableResult<TKeySchema, TValueSchema> {
const schemaName = transformSchemaName(`${getAddress(address)}__${namespace}`);
// We intentionally do not snake case the namespace due to potential conflicts
// with namespaces of a similar name (e.g. `MyNamespace` vs. `my_namespace`).
// TODO: consider snake case when we resolve https://github.com/latticexyz/mud/issues/1991
const schemaName = transformSchemaName(`${address.toLowerCase()}__${namespace}`);
const tableName = snakeCase(name);

// Column names, however, are safe to snake case because they're scoped to tables, defined once per table, and there's a limited number of fields in total.

const keyColumns = Object.fromEntries(
Object.entries(keySchema).map(([name, type]) => [name, buildColumn(name, type).notNull()])
Object.entries(keySchema).map(([name, type]) => [name, buildColumn(snakeCase(name), type).notNull()])
);

const valueColumns = Object.fromEntries(
Object.entries(valueSchema).map(([name, type]) => [name, buildColumn(name, type).notNull()])
Object.entries(valueSchema).map(([name, type]) => [name, buildColumn(snakeCase(name), type).notNull()])
);

// TODO: make sure there are no meta columns that overlap with key/value columns
Expand All @@ -64,7 +70,7 @@ export function buildTable<TKeySchema extends KeySchema, TValueSchema extends Va
...metaColumns,
};

const table = pgSchema(schemaName).table(name, columns);
const table = pgSchema(schemaName).table(tableName, columns);

return table as PgTableFromSchema<TKeySchema, TValueSchema>;
}
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7b73f44

Please sign in to comment.