<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
  <title>pgcast changelog</title>
  <subtitle>Zero-abstraction Postgres for TypeScript: types cast from your database schema.</subtitle>
  <link rel="self" type="application/atom+xml" href="https://pgcast.dev/changelog.xml"/>
  <link rel="alternate" type="text/html" href="https://pgcast.dev/changelog"/>
  <id>https://pgcast.dev/changelog</id>
  <updated>2026-11-12T00:00:00Z</updated>
  <author><name>pgcast</name><uri>https://github.com/AlexandreDemartin/pgcast</uri></author>
  <entry>
    <title>pgcast 6.6.2</title>
    <link rel="alternate" type="text/html" href="https://pgcast.dev/changelog#6.6.2"/>
    <id>https://pgcast.dev/changelog#6.6.2</id>
    <updated>2026-11-12T00:00:00Z</updated>
    <content type="html">&lt;h3&gt;Changed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Package renamed to &lt;code&gt;pgcast&lt;/code&gt;, published on npm as &lt;code&gt;@pgcast/pgcast&lt;/code&gt; (npm refuses the unscoped name, too close to &lt;code&gt;recast&lt;/code&gt;); install it as &lt;code&gt;zapatos@npm:@pgcast/pgcast&lt;/code&gt;. The CLI is available as both &lt;code&gt;pgcast&lt;/code&gt; and &lt;code&gt;zapatos&lt;/code&gt;. Module names (&lt;code&gt;zapatos/db&lt;/code&gt;, &lt;code&gt;zapatos/schema&lt;/code&gt;, &lt;code&gt;zapatos/custom&lt;/code&gt;) are unchanged.&lt;/li&gt;
&lt;li&gt;Toolchain: TypeScript 5.9, ESLint 9 (native flat config), typescript-eslint 8, &lt;code&gt;@stylistic/eslint-plugin&lt;/code&gt;. The toolchain change alone leaves the build output (&lt;code&gt;dist/&lt;/code&gt;, &lt;code&gt;db.mjs&lt;/code&gt;, &lt;code&gt;generate.mjs&lt;/code&gt;) byte-identical to 6.6.1; the fixes below change &lt;code&gt;dist/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Removed&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The noisy &lt;code&gt;postinstall&lt;/code&gt; message.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;postversion&lt;/code&gt; script that pushed and published to npm automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Added&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prepack&lt;/code&gt; builds before any &lt;code&gt;npm pack&lt;/code&gt; / &lt;code&gt;npm publish&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Vitest harness (&lt;code&gt;test&lt;/code&gt;, &lt;code&gt;test:watch&lt;/code&gt;, &lt;code&gt;test:types&lt;/code&gt;) and a &lt;code&gt;typecheck&lt;/code&gt; script.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; (+ &lt;code&gt;CLAUDE.md&lt;/code&gt; symlink) and design decision notes under &lt;code&gt;docs/&lt;/code&gt;. The roadmap lives in GitHub issues: one epic per chapter, one sub-issue per step, closed by its pull request.&lt;/li&gt;
&lt;li&gt;CI matrix: Node 20, 22 and latest LTS; draft PRs run no job.&lt;/li&gt;
&lt;li&gt;Agent workflow: &lt;code&gt;.agents/skills/&lt;/code&gt;, &lt;code&gt;.claude/agents/&lt;/code&gt; (&lt;code&gt;builder&lt;/code&gt;, &lt;code&gt;branch-reviewer&lt;/code&gt;, &lt;code&gt;chores&lt;/code&gt;), &lt;code&gt;.claude/hooks/guard.mjs&lt;/code&gt;, &lt;code&gt;npm run check:agents&lt;/code&gt;, &lt;code&gt;docs/LEARNINGS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Step workflow, drawn in &lt;code&gt;docs/WORKFLOW.md&lt;/code&gt;: an issue template with &lt;strong&gt;Files&lt;/strong&gt; and &lt;strong&gt;Risk&lt;/strong&gt;, a plan comment before code, &lt;strong&gt;Proofs&lt;/strong&gt; in each PR, review comments in Conventional Comments, a merge gate by risk (Ship / Show / Ask) and squash merges. &lt;code&gt;npm run check:agents&lt;/code&gt; reports the size of each skill and agent.&lt;/li&gt;
&lt;li&gt;Integration test harness: reference fixture &lt;code&gt;test/fixtures/schema.sql&lt;/code&gt; (Postgres 14+), a template database built once per run, a fresh database per test file and a &lt;code&gt;ROLLBACK&lt;/code&gt; per test (&lt;code&gt;useTestDatabase()&lt;/code&gt;, &lt;code&gt;test/helpers/db.ts&lt;/code&gt;). Needs &lt;code&gt;TEST_DATABASE_URL&lt;/code&gt; (the environment, else &lt;code&gt;src/.env&lt;/code&gt;); tests only create and drop &lt;code&gt;pgcast_test_*&lt;/code&gt; databases.&lt;/li&gt;
&lt;li&gt;CI matrix: Postgres 14, 16 and 18 service containers × Node 20, 22 and latest LTS.&lt;/li&gt;
&lt;li&gt;Codegen characterization (&lt;code&gt;test/integration/codegen.test.ts&lt;/code&gt;): the output for the fixture is frozen per configuration in &lt;code&gt;test/__snapshots__/codegen/&lt;/code&gt;; the main configuration's files, as &lt;code&gt;generate()&lt;/code&gt; writes them, are committed in &lt;code&gt;test/generated/&lt;/code&gt; and compiled by &lt;code&gt;npm run typecheck&lt;/code&gt; against the declarations of &lt;code&gt;src/db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Injection characterization: the compiled text of every entry point of &lt;code&gt;core.ts&lt;/code&gt; and &lt;code&gt;shortcuts.ts&lt;/code&gt; that writes a caller's string into SQL, under hostile inputs (&lt;code&gt;test/unit/injection.test.ts&lt;/code&gt;), and what those inputs achieve against Postgres, with and without parameters (&lt;code&gt;test/integration/injection.test.ts&lt;/code&gt;). Nothing is fixed yet.&lt;/li&gt;
&lt;li&gt;Runtime characterization: the &lt;code&gt;text&lt;/code&gt; + &lt;code&gt;values&lt;/code&gt; of each &lt;code&gt;core.ts&lt;/code&gt; branch, shortcut and condition, frozen by inline snapshots (&lt;code&gt;test/unit/&lt;/code&gt;), then run against the fixture (&lt;code&gt;test/integration/&lt;/code&gt;), surprises included (&lt;code&gt;// characterization:&lt;/code&gt; comments); &lt;code&gt;transaction()&lt;/code&gt; commit, rollback, pass-through, retries and listener messages. The harness exposes the file's pool for code that commits (&lt;code&gt;useTestDatabase().pool&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Type tests (&lt;code&gt;test/types/&lt;/code&gt;, &lt;code&gt;npm run test:types&lt;/code&gt;): the public types, positive and negative, on the fixture's generated schema and the emitted declarations of &lt;code&gt;src/db&lt;/code&gt;, as a consumer compiles them; &lt;code&gt;test:types&lt;/code&gt; emits those declarations first. Every codegen variant but &lt;code&gt;edge&lt;/code&gt; is compiled with its custom types, and orphan snapshot files are reported.&lt;/li&gt;
&lt;li&gt;Type-checking cost: &lt;code&gt;npm run bench:types&lt;/code&gt; compiles &lt;code&gt;bench/&lt;/code&gt;, a few dozen typed queries on the fixture's generated schema (nested &lt;code&gt;lateral&lt;/code&gt;, &lt;code&gt;columns&lt;/code&gt;, &lt;code&gt;extras&lt;/code&gt;, writes with &lt;code&gt;returning&lt;/code&gt;, conditions, &lt;code&gt;sql&amp;lt;…&amp;gt;&lt;/code&gt;), and fails when &lt;code&gt;Instantiations&lt;/code&gt; or &lt;code&gt;Types&lt;/code&gt;, net of TypeScript's libs and &lt;code&gt;@types&lt;/code&gt;, grow by more than 20 % on &lt;code&gt;bench/baseline.json&lt;/code&gt; (measured on the 6.6.1 code); also fails when they drop by more than 5 % or when the TypeScript version differs, so that the baseline follows (&lt;code&gt;-- --update&lt;/code&gt;). Runs in CI as its own job, without Postgres.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Security&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lock.for&lt;/code&gt; and &lt;code&gt;lock.wait&lt;/code&gt; of &lt;code&gt;select()&lt;/code&gt;, the options of &lt;code&gt;truncate()&lt;/code&gt; and the isolation level of &lt;code&gt;transaction()&lt;/code&gt; are checked at run time, as &lt;code&gt;order.direction&lt;/code&gt; already was.
&lt;ul&gt;
&lt;li&gt;A value the type refuses now throws before any query, instead of being written raw into the SQL (where &lt;code&gt;lock.for&lt;/code&gt; could run a second statement).&lt;/li&gt;
&lt;li&gt;The SQL for valid values is unchanged, as it is for an &lt;code&gt;undefined&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt; that gets past the type without &lt;code&gt;strictNullChecks&lt;/code&gt; (&lt;code&gt;lock.wait&lt;/code&gt;, &lt;code&gt;truncate()&lt;/code&gt; options).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;aggregate&lt;/code&gt; of &lt;code&gt;select()&lt;/code&gt;, its fifth argument, must now be a function name as Postgres lexes it.
&lt;ul&gt;
&lt;li&gt;Plain, quoted or &lt;code&gt;U&amp;amp;&lt;/code&gt; quoted, in up to three dotted parts (&lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;my$agg&lt;/code&gt;, &lt;code&gt;my_schema.&amp;quot;My agg&amp;quot;&lt;/code&gt;, &lt;code&gt;my_db.my_schema.my_agg&lt;/code&gt;), with or without spaces around it and its dots.&lt;/li&gt;
&lt;li&gt;Anything else, &lt;code&gt;U&amp;amp;&amp;quot;…&amp;quot; UESCAPE '…'&lt;/code&gt; included, throws before any query, instead of being written raw into the SQL (where it could run a second statement).&lt;/li&gt;
&lt;li&gt;The SQL of &lt;code&gt;count()&lt;/code&gt;, &lt;code&gt;sum()&lt;/code&gt;, &lt;code&gt;avg()&lt;/code&gt;, &lt;code&gt;min()&lt;/code&gt;, &lt;code&gt;max()&lt;/code&gt; and of a valid name is unchanged, byte for byte, as it is for &lt;code&gt;null&lt;/code&gt;; outside &lt;code&gt;SelectResultMode.Numeric&lt;/code&gt; the argument is still ignored.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The type of &lt;code&gt;param(x, cast)&lt;/code&gt; can no longer close its quotes.
&lt;ul&gt;
&lt;li&gt;A type that is well-formed once wrapped in &lt;code&gt;&amp;quot;&lt;/code&gt; (non-empty quoted identifiers joined by &lt;code&gt;.&lt;/code&gt;, with or without whitespace around it, such as &lt;code&gt;pg_catalog&amp;quot;.&amp;quot;int4&lt;/code&gt;) is written as is, as before: &lt;code&gt;CAST($1 AS &amp;quot;pg_catalog&amp;quot;.&amp;quot;int4&amp;quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Any other is quoted as one identifier, with any &lt;code&gt;&amp;quot;&lt;/code&gt; doubled (as &lt;code&gt;quote_ident&lt;/code&gt; does): &lt;code&gt;param(1, 'int4&amp;quot;) || (SELECT secret FROM t) || (&amp;quot;int4')&lt;/code&gt; now compiles to &lt;code&gt;CAST($1 AS &amp;quot;int4&amp;quot;&amp;quot;) || (SELECT secret FROM t) || (&amp;quot;&amp;quot;int4&amp;quot;)&lt;/code&gt;, a type that does not exist (42704), instead of a cast that reads another expression.&lt;/li&gt;
&lt;li&gt;A type without &lt;code&gt;&amp;quot;&lt;/code&gt; compiles as before.&lt;/li&gt;
&lt;li&gt;The only casts that ran under 6.6.1 and no longer do have something other than whitespace between their quoted parts: an expression, which is the hole this closes, or a comment (&lt;code&gt;pg_catalog&amp;quot;/**/.&amp;quot;int4&lt;/code&gt;), now a type that does not exist (42704).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A table or column name given as a string (&lt;code&gt;Table&lt;/code&gt;, &lt;code&gt;Column&lt;/code&gt;, a &lt;code&gt;Whereable&lt;/code&gt; key, &lt;code&gt;cols&lt;/code&gt;, &lt;code&gt;parent()&lt;/code&gt;, &lt;code&gt;columns&lt;/code&gt;, &lt;code&gt;order.by&lt;/code&gt;, …) can no longer close its quotes: any &lt;code&gt;&amp;quot;&lt;/code&gt; in it is doubled, as &lt;code&gt;quote_ident&lt;/code&gt; does, and it is still split on &lt;code&gt;.&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Whereable&lt;/code&gt; key &lt;code&gt;a&amp;quot; = &amp;quot;a&amp;quot; OR TRUE OR &amp;quot;a&lt;/code&gt; now compiles to &lt;code&gt;WHERE (&amp;quot;a&amp;quot;&amp;quot; = &amp;quot;&amp;quot;a&amp;quot;&amp;quot; OR TRUE OR &amp;quot;&amp;quot;a&amp;quot; = $1)&lt;/code&gt;, a column that does not exist (42703), instead of &lt;code&gt;WHERE (&amp;quot;a&amp;quot; = &amp;quot;a&amp;quot; OR TRUE OR &amp;quot;a&amp;quot; = $1)&lt;/code&gt;, which returned every row; without parameters, such a name could run a second statement.&lt;/li&gt;
&lt;li&gt;A name without &lt;code&gt;&amp;quot;&lt;/code&gt; compiles as before, as does one that 6.6.1 already compiled to quoted names only (a hand-escaped &lt;code&gt;a&amp;quot;&amp;quot;b&lt;/code&gt;, for the column &lt;code&gt;a&amp;quot;b&lt;/code&gt;) and one that is already a well-formed quoted name (below).&lt;/li&gt;
&lt;li&gt;The cost: a real column named &lt;code&gt;a&amp;quot;&amp;quot;b&lt;/code&gt;, which the generated &lt;code&gt;Column&lt;/code&gt; type spells &lt;code&gt;'a&amp;quot;&amp;quot;b'&lt;/code&gt;, still compiles to &lt;code&gt;&amp;quot;a&amp;quot;&amp;quot;b&amp;quot;&lt;/code&gt;, the column &lt;code&gt;a&amp;quot;b&lt;/code&gt;, as under 6.6.1.&lt;/li&gt;
&lt;li&gt;The only names that ran under 6.6.1 and no longer do close their quotes to splice SQL around them: an alias (&lt;code&gt;id&amp;quot; AS &amp;quot;x&lt;/code&gt;), a table alias (&lt;code&gt;users&amp;quot; &amp;quot;u&lt;/code&gt;), a cast (&lt;code&gt;id&amp;quot;::&amp;quot;text&lt;/code&gt;), a sort or a second item (&lt;code&gt;id&amp;quot; DESC, &amp;quot;name&lt;/code&gt;), a collation (&lt;code&gt;display_name&amp;quot; COLLATE &amp;quot;C&lt;/code&gt;), an expression (&lt;code&gt;display_name&amp;quot; || &amp;quot;email&lt;/code&gt;) or a comment (&lt;code&gt;users&amp;quot;/**/.&amp;quot;id&lt;/code&gt;), each now a column or table that does not exist (42703, 42P01).&lt;/li&gt;
&lt;li&gt;The generated &lt;code&gt;Table&lt;/code&gt; and &lt;code&gt;Column&lt;/code&gt; types, unions of literal names, already refused all of them; write those in a &lt;code&gt;sql&lt;/code&gt; template, with only the name interpolated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;lateral&lt;/code&gt; keys of &lt;code&gt;select()&lt;/code&gt; (and of &lt;code&gt;selectOne()&lt;/code&gt;, &lt;code&gt;count()&lt;/code&gt;, …), its &lt;code&gt;alias&lt;/code&gt; and its table name can no longer close their quotes where they become the aliases &lt;code&gt;&amp;quot;lateral_…&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;sq_…&amp;quot;&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;Each is one identifier there, any &lt;code&gt;&amp;quot;&lt;/code&gt; doubled as &lt;code&gt;quote_ident&lt;/code&gt; does, not split on &lt;code&gt;.&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;alias&lt;/code&gt; &lt;code&gt;u&amp;quot;; DROP TABLE users; --&lt;/code&gt; now ends &lt;code&gt;AS &amp;quot;sq_u&amp;quot;&amp;quot;; DROP TABLE users; --&amp;quot;&lt;/code&gt;, one alias, as it is everywhere else in the query; with the fix above alone, it ended &lt;code&gt;AS &amp;quot;sq_u&amp;quot;; DROP TABLE users; --&amp;quot;&lt;/code&gt; and, without parameters, ran a second statement.&lt;/li&gt;
&lt;li&gt;A name without &lt;code&gt;&amp;quot;&lt;/code&gt; compiles as before, as does one that 6.6.1 already wrote as one quoted name (&lt;code&gt;a&amp;quot;&amp;quot;b&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Replayed in their 6.6.1 form, no key, alias or table name that returned rows under 6.6.1 stops, but one that spliced SQL around &lt;code&gt;&amp;quot;lateral_…&amp;quot;&lt;/code&gt; or &lt;code&gt;&amp;quot;sq_…&amp;quot;&lt;/code&gt; and still ran, with parameters too, now runs as one name, with a different result:
&lt;ul&gt;
&lt;li&gt;Before this fix, a lateral key could read other tables: &lt;code&gt;selectOne('users', { id: 1 }, { columns: ['id'], lateral: { [key]: count(…) } })&lt;/code&gt;, with the key &lt;code&gt;x&amp;quot; || ') AS result FROM &amp;quot;users&amp;quot; LEFT JOIN LATERAL (SELECT (SELECT string_agg(email::text, chr(44)) FROM users) AS result) AS &amp;quot;lateral_x&lt;/code&gt;, opened a string literal in one &lt;code&gt;&amp;quot;lateral_…&amp;quot;&lt;/code&gt; and closed it in the other, and returned every user's email under the key; it now returns the count.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;alias&lt;/code&gt; &lt;code&gt;x&amp;quot; (&amp;quot;result&amp;quot;) GROUP BY &amp;quot;result&lt;/code&gt;, with &lt;code&gt;columns: ['email']&lt;/code&gt;, returned one user, and now returns them all.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A key or alias with a lone &lt;code&gt;&amp;quot;&lt;/code&gt; (&lt;code&gt;a&amp;quot;b&lt;/code&gt;) was a syntax error under 6.6.1 (42601), and now runs; &lt;code&gt;select()&lt;/code&gt; also reaches a table whose name has a &lt;code&gt;&amp;quot;&lt;/code&gt; (&lt;code&gt;'my&amp;quot;table'&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The cost, as for a string identifier: &lt;code&gt;a&amp;quot;&amp;quot;b&lt;/code&gt; reads as a hand-escaped &lt;code&gt;a&amp;quot;b&lt;/code&gt;, so the lateral keys &lt;code&gt;a&amp;quot;b&lt;/code&gt; and &lt;code&gt;a&amp;quot;&amp;quot;b&lt;/code&gt; given together get the same alias, which Postgres refuses (42712).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A string that starts and ends with &lt;code&gt;&amp;quot;&lt;/code&gt; is written as is only if it is already a well-formed name as Postgres lexes it; any other is quoted like any other name (above).
&lt;ul&gt;
&lt;li&gt;Well-formed: a quoted identifier, then any quoted, &lt;code&gt;U&amp;amp;&lt;/code&gt; quoted or unquoted ones, each after a &lt;code&gt;.&lt;/code&gt;, with or without whitespace around it (&lt;code&gt;&amp;quot;a&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;a&amp;quot;.&amp;quot;b&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;a&amp;quot;&amp;quot;b&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;a.b&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;public&amp;quot;.users.&amp;quot;display_name&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;users&amp;quot;.U&amp;amp;&amp;quot;display_name&amp;quot;&lt;/code&gt;), the parts the &lt;code&gt;aggregate&lt;/code&gt; of &lt;code&gt;select()&lt;/code&gt; accepts.&lt;/li&gt;
&lt;li&gt;6.6.1 wrote as is anything that started and ended with &lt;code&gt;&amp;quot;&lt;/code&gt;, even a lone &lt;code&gt;&amp;quot;&lt;/code&gt;: without parameters, the column &lt;code&gt;&amp;quot;display_name&amp;quot; FROM users; DROP TABLE users; SELECT &amp;quot;display_name&amp;quot;&lt;/code&gt; ran a second statement, and now compiles to &lt;code&gt;&amp;quot;&amp;quot;&amp;quot;display_name&amp;quot;&amp;quot; FROM users; DROP TABLE users; SELECT &amp;quot;&amp;quot;display_name&amp;quot;&amp;quot;&amp;quot;&lt;/code&gt;, a column that does not exist (42703).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;; DROP TABLE t; --&amp;quot;&lt;/code&gt; is one quoted name, the column &lt;code&gt;; DROP TABLE t; --&lt;/code&gt;, and is still written as is.&lt;/li&gt;
&lt;li&gt;Replayed against Postgres, the only such strings that ran under 6.6.1 and no longer do close their quotes to splice SQL around them: a statement (above) or a clause (&lt;code&gt;&amp;quot;display_name&amp;quot; FROM &amp;quot;users&amp;quot; UNION SELECT &amp;quot;display_name&amp;quot;&lt;/code&gt;), an alias (&lt;code&gt;&amp;quot;display_name&amp;quot; AS &amp;quot;x&amp;quot;&lt;/code&gt;), a table alias (&lt;code&gt;&amp;quot;users&amp;quot; &amp;quot;u&amp;quot;&lt;/code&gt;), a cast (&lt;code&gt;&amp;quot;display_name&amp;quot;::&amp;quot;text&amp;quot;&lt;/code&gt;), a sort or a second item (&lt;code&gt;&amp;quot;display_name&amp;quot; DESC, &amp;quot;email&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;display_name&amp;quot;, &amp;quot;email&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;users&amp;quot;, &amp;quot;accounts&amp;quot;&lt;/code&gt;), a collation (&lt;code&gt;&amp;quot;display_name&amp;quot; COLLATE &amp;quot;C&amp;quot;&lt;/code&gt;), an expression (&lt;code&gt;&amp;quot;display_name&amp;quot; || &amp;quot;email&amp;quot;&lt;/code&gt;, &lt;code&gt;UESCAPE&lt;/code&gt; clauses included), a join (&lt;code&gt;&amp;quot;users&amp;quot; NATURAL JOIN &amp;quot;accounts&amp;quot;&lt;/code&gt;) or a comment between the parts (&lt;code&gt;&amp;quot;users&amp;quot;/**/.&amp;quot;display_name&amp;quot;&lt;/code&gt;), each now a column or table that does not exist (42703, 42P01).&lt;/li&gt;
&lt;li&gt;Strings that were a syntax error under 6.6.1 now name something that does not exist: &lt;code&gt;&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; the column &lt;code&gt;&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;a&amp;quot;b&amp;quot;&lt;/code&gt; the column &lt;code&gt;&amp;quot;a&amp;quot;b&amp;quot;&lt;/code&gt;; as the &lt;code&gt;alias&lt;/code&gt; of &lt;code&gt;select()&lt;/code&gt;, they now run as one alias, as does &lt;code&gt;&amp;quot;u&amp;quot; AS &amp;quot;v&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A plain value in the &lt;code&gt;updateValues&lt;/code&gt; of &lt;code&gt;upsert()&lt;/code&gt; is now sent as a parameter, as its type and &lt;code&gt;update()&lt;/code&gt; already said.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{ mood: 'happy' }&lt;/code&gt; compiles to &lt;code&gt;DO UPDATE SET (&amp;quot;mood&amp;quot;) = ROW($n)&lt;/code&gt;, instead of &lt;code&gt;ROW(&amp;quot;happy&amp;quot;)&lt;/code&gt;, an identifier.&lt;/li&gt;
&lt;li&gt;Under 6.6.1, such a string could close its quotes: &lt;code&gt;xmin&amp;quot; || (SELECT string_agg(email::text, ',') FROM users) || &amp;quot;xmin&lt;/code&gt; wrote every user's email into the column.&lt;/li&gt;
&lt;li&gt;Under 6.6.1, a number, a &lt;code&gt;bigint&lt;/code&gt; or a boolean threw &amp;quot;Alien object&amp;quot;, &lt;code&gt;null&lt;/code&gt; a &lt;code&gt;TypeError&lt;/code&gt;, an object or a class instance (a &lt;code&gt;Date&lt;/code&gt;, a &lt;code&gt;Buffer&lt;/code&gt;, a custom type with &lt;code&gt;toPostgres()&lt;/code&gt;) became a &lt;code&gt;WHERE&lt;/code&gt;-style condition of its keys (&lt;code&gt;TRUE&lt;/code&gt; without keys), and an array its items one after the other (&lt;code&gt;['a', 'b']&lt;/code&gt; gave the identifier &lt;code&gt;&amp;quot;a&amp;quot;&amp;quot;b&amp;quot;&lt;/code&gt;, &lt;code&gt;[1, 2]&lt;/code&gt; threw, &lt;code&gt;[]&lt;/code&gt; gave &lt;code&gt;ROW()&lt;/code&gt;, a syntax error): each is now a parameter, with the same JSON casts as in &lt;code&gt;update()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A value that holds a &lt;code&gt;sql&lt;/code&gt;, &lt;code&gt;param()&lt;/code&gt;, &lt;code&gt;raw()&lt;/code&gt;, &lt;code&gt;cols()&lt;/code&gt;, &lt;code&gt;vals()&lt;/code&gt; or &lt;code&gt;parent()&lt;/code&gt; object, &lt;code&gt;Default&lt;/code&gt; or &lt;code&gt;self&lt;/code&gt;, at any depth of its arrays and plain objects, compiles as before, byte for byte: &lt;code&gt;[db.sql`upper(EXCLUDED.name)`]&lt;/code&gt;, &lt;code&gt;{ 'users.name': db.conditions.like('B%') }&lt;/code&gt;, or what &lt;code&gt;mapWithSeparator()&lt;/code&gt; returns when it holds a fragment (a &lt;code&gt;sql&lt;/code&gt; separator, say) or a &lt;code&gt;param()&lt;/code&gt;; so a string beside a fragment in an array is still an identifier.&lt;/li&gt;
&lt;li&gt;What &lt;code&gt;mapWithSeparator()&lt;/code&gt; returns for one string, or for none, is a plain array, now a parameter.&lt;/li&gt;
&lt;li&gt;A function or the global object, alone or in an array, still throws.&lt;/li&gt;
&lt;li&gt;Replayed against Postgres on a text column, a bare column name never ran (&lt;code&gt;ROW(&amp;quot;display_name&amp;quot;)&lt;/code&gt; is ambiguous between the existing row and &lt;code&gt;EXCLUDED&lt;/code&gt;, 42702), but these did, and are now stored as data:
&lt;ul&gt;
&lt;li&gt;a qualified column (&lt;code&gt;'users.display_name'&lt;/code&gt;, &lt;code&gt;'public.users.display_name'&lt;/code&gt;, &lt;code&gt;'excluded.display_name'&lt;/code&gt;), a whole row (&lt;code&gt;'users'&lt;/code&gt;, &lt;code&gt;'excluded'&lt;/code&gt;) or a system column (&lt;code&gt;'xmin'&lt;/code&gt;, &lt;code&gt;'ctid'&lt;/code&gt;), which read that column as text;&lt;/li&gt;
&lt;li&gt;an object with no own enumerable keys (&lt;code&gt;{}&lt;/code&gt;, a &lt;code&gt;Date&lt;/code&gt;, an empty &lt;code&gt;Buffer&lt;/code&gt;, a &lt;code&gt;Map&lt;/code&gt;, a &lt;code&gt;Set&lt;/code&gt;), stored as &lt;code&gt;true&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;an object or a class instance whose keys all name such a column (&lt;code&gt;{ 'users.display_name': 'Bob' }&lt;/code&gt;, &lt;code&gt;{ xmin: '1' }&lt;/code&gt;), stored as &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a class instance that holds a fragment (&lt;code&gt;new (class { x = db.sql`'Bobby'` })()&lt;/code&gt;), which stored the fragment's result;&lt;/li&gt;
&lt;li&gt;or an array of one of them, what &lt;code&gt;mapWithSeparator()&lt;/code&gt; returns for one string included.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;To keep the 6.6.1 result, put the value in a fragment: any value other than &lt;code&gt;undefined&lt;/code&gt; (which still means the key is absent) compiles inside &lt;code&gt;db.sql`${value}` &lt;/code&gt; byte for byte as it did in &lt;code&gt;updateValues&lt;/code&gt; under 6.6.1, the name fixes above aside. A qualified column goes in as its parts: &lt;code&gt;{ tagline: db.sql`${'films'}.${'title'}` }&lt;/code&gt; for &lt;code&gt;{ tagline: 'films.title' }&lt;/code&gt;, as the &lt;code&gt;SQL&lt;/code&gt; type of &lt;code&gt;sql&lt;/code&gt; refuses &lt;code&gt;db.sql`${'films.title'}` &lt;/code&gt; (TS2345), in Zapatos 6.6.1 too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
</feed>
