Skip to content

Support for quoted identifiers#1307

Open
matejcerny wants to merge 1 commit into
typelevel:mainfrom
matejcerny:quoted-identifiers
Open

Support for quoted identifiers#1307
matejcerny wants to merge 1 commit into
typelevel:mainfrom
matejcerny:quoted-identifiers

Conversation

@matejcerny

Copy link
Copy Markdown

implements #1276

@matejcerny matejcerny force-pushed the quoted-identifiers branch from 059f5d6 to a19d05f Compare May 13, 2026 19:33
def asSql: String = if (quoted) s"\"${value.replace("\"", "\"\"")}\"" else value
override def toString: String = asSql

override def equals(other: Any): Boolean = other match {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this can break anything?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not add quoted as a constructor parameter instead and rely on the default one?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would break MiMa

@kubukoz kubukoz May 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a sealed class, it should be fine...

(but maybe it's not)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not 😞

val identifier: SCodec[Identifier] =
utf8z.exmap(
s => Attempt.fromEither(Identifier.fromString(s).leftMap(Err(_))),
s => Attempt.fromEither(Identifier.fromString(s).orElse(Identifier.fromStringQuoted(s)).leftMap(Err(_))),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea if there is a better solution?

@matejcerny matejcerny force-pushed the quoted-identifiers branch from a19d05f to 214acdf Compare May 13, 2026 19:41
@matejcerny

Copy link
Copy Markdown
Author

It's a much bigger change than I originally thought 🤷

Identifier.fromStringQuoted(part) match {
case Right(Identifier(s)) => '{ Identifier.fromStringQuoted(${Expr(s)}).fold(sys.error, identity) }
case Left(s) =>
report.error(s)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a variant that aborts and returns Nothing, so you don't have to do '{???} - errorAndAbort

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mirrored the identifier_impl implementation

def asSql: String = if (quoted) s"\"${value.replace("\"", "\"\"")}\"" else value
override def toString: String = asSql

override def equals(other: Any): Boolean = other match {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not add quoted as a constructor parameter instead and rely on the default one?

@matejcerny matejcerny force-pushed the quoted-identifiers branch from 214acdf to 88a1522 Compare June 10, 2026 14:57
@matejcerny matejcerny force-pushed the quoted-identifiers branch from 88a1522 to 586337c Compare June 28, 2026 12:56
def notify(message: String): F[Unit] =
// TODO: escape the message
proto.execute(Command(s"NOTIFY ${name.value}, '$message'", Origin.unknown, Void.codec)).void
proto.execute(Command(s"NOTIFY ${name.asSql}, '$message'", Origin.unknown, Void.codec)).void

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we need to support Identifiers in interpolation wherever Fragment[Void] is allowed. But that's probably a separate issue.

Comment on lines +12 to +13
def quoted: Boolean = false
def asSql: String = if (quoted) s"\"${value.replace("\"", "\"\"")}\"" else value

@nigredo-tori nigredo-tori Jul 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need to treat quoted identifiers as a separate case? As I understand it (could anyone more familiar with PostgreSQL please confirm this?), the distinction exists only on syntactical level:

  1. Unquoted identifiers are automatically converted to lowercase.
  2. Unquoted identifiers can not contain keywords.
  3. Quoted identifiers are not converted, and can contain almost anything.

Which means that foo_bar is for all intents and purposes the same as "foo_bar".

I propose we do the following (starting from main):

  1. Change the existing id"..." syntax to automatically lowercase the literal. Maybe mark this syntax as deprecated, since the same behavior can be achieved if the calling code provides the lowercase literal.
  2. Add a separate syntax that does not change the literal. ( Not sure what to callit. qid"..." doesn't really work, since quoting becomes an implementation detail).
  3. When interpolating an Identifier into SQL, quote it iff it requires quoting (if the value contains uppercase letters, or otherwise breaks the constraints on unquoted identifiers). We can maybe cache the possibly quoted form on Identifier creation.
  4. Instead of 3, we can just always quote Identifiers. It's noisy WRT generated SQL, but a bit easier to implement.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants