diff --git a/docs/configuration.md b/docs/configuration.md index e05fe27..12085dd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -218,9 +218,10 @@ target/generated-sources/sqlcj/dev/example/generated/OrderRepository.java Both repositories may contain a query named `GetById`, because each name is resolved inside its own repository. -The entries share one generated package, so they also share its row records: a -table whose complete row both entries return generates one `Row` -file that both repositories return. +The entries share one generated package, so they also share its row records and +its enums: a table whose complete row both entries return generates one +`Row` file that both repositories return, and an enum type both +entries use generates one Java enum file that both repositories use. ## Generated Java Names @@ -261,6 +262,14 @@ uses no locale-dependent case mapping and no configuration: sqlcj: Invalid query group 'User' in /home/dev/project/sql/queries.sql: SQL name '***' of query 'ListUsers' has no letter or digit to generate a Java name from ``` + An enum label is rejected the same way, and so are two labels of one enum + type that generate one constant: + + ```text + sqlcj: Invalid query group 'Stage' in /home/dev/project/sql/queries.sql: Label '***' of enum type 'stage_setting' has no letter or digit to generate a Java constant from + sqlcj: Invalid query group 'Stage' in /home/dev/project/sql/queries.sql: Labels 'in progress' and 'in-progress' of enum type 'stage_setting' generate the same constant IN_PROGRESS + ``` + The rules are applied as follows: - the repository type is the upper camel form of `sql[].name` followed by @@ -272,6 +281,14 @@ The rules are applied as follows: singularized. A row record is a top-level type of `java.package`, generated once per table into its own file and shared by every repository of the package that returns that row, +- an enum type is the upper camel form of its PostgreSQL name, so + `stage_setting` generates `StageSetting`. It is a top-level type of + `java.package`, generated once per enum type into its own file, and only for + an enum type a query reads or binds, +- an enum constant is the label's words, upper-cased and joined with `_`, so the + labels `in progress` and `in-progress` both generate `IN_PROGRESS`, the label + `InProgress`, which is one word, generates `INPROGRESS`, and the label `2fast` + generates `_2FAST`, - a method is the lower camel form of the query name, so `get_author` generates `getAuthor`, - a row-mapper field is the method name followed by `RowMapper`, and the mapper @@ -340,6 +357,16 @@ the package, the two tables may come from one entry or from two: sqlcj: Invalid query group 'User' in /home/dev/project/sql/queries.sql: Tables 'user_data' and 'userdata' generate row types that are equal ignoring case: UserDataRow and UserdataRow ``` +Two enum types whose Java enums are equal ignoring case, and an enum type whose +Java enum is equal ignoring case to a repository, row record, or nested result +record of the package, are rejected on the same grounds, in either generation +order: + +```text +sqlcj: Invalid query group 'Stage' in /home/dev/project/sql/queries.sql: Enum types 'stage_setting' and 'stagesetting' generate enum types that are equal ignoring case: StageSetting and Stagesetting +sqlcj: Invalid query group 'Stage' in /home/dev/project/sql/queries.sql: Enum type 'users_row' generates UsersRow, which is equal ignoring case to the generated type UsersRow +``` + ### Row record collisions Two entries of one package that return the complete row of one table generate @@ -351,6 +378,13 @@ the run ends, naming the entry that defined the table first: sqlcj: Invalid query group 'Library' in /home/dev/project/sql/library.sql: Table 'authors' differs from its definition in query group 'Author', which generates the same row type AuthorsRow ``` +Two entries that use one enum type must define its labels alike for the same +reason, because the package generates one Java enum for it: + +```text +sqlcj: Invalid query group 'Library' in /home/dev/project/sql/library.sql: Enum type 'stage_setting' differs from its definition in query group 'Author', which generates the same enum type StageSetting +``` + A cross-entry failure is reported against the later entry in configuration order and, like every generation failure, ends the run before any file is written. @@ -408,11 +442,13 @@ files the run had already written stay in place. A successful run records what it generated in `sqlcj-manifest.txt` inside `java.out`. The manifest is a UTF-8 text file listing every generated file, -repositories and row records alike, as a path relative to `java.out`, with `/` +repositories, row records, and enums alike, as a path relative to `java.out`, +with `/` between its name elements, sorted, one per line. The next successful run deletes the files the previous manifest listed that it did not generate itself, so a renamed or removed configuration entry leaves no stale repository behind, and a -row record no entry returns any more is deleted too. +row record no entry returns any more, and an enum no entry uses any more, are +deleted too. Cleanup is deliberately narrow: diff --git a/docs/postgresql.md b/docs/postgresql.md index a24b43d..d93ac69 100644 --- a/docs/postgresql.md +++ b/docs/postgresql.md @@ -133,26 +133,139 @@ accepted and map exactly like their unparameterized spellings. | `BYTEA` | `byte[]` | A record compares an array component by reference, so two row records holding equal bytes are not `equals`. | | `JSON` | `String` | The JSON text itself. PostgreSQL stores it as written, so it reads back exactly as written. sqlcj never parses, validates, or normalizes it. | | `JSONB` | `String` | The JSON text itself. PostgreSQL stores a decomposed value, so the text reads back as PostgreSQL renders it rather than as written, and `=` compares by value. sqlcj never parses, validates, or normalizes it. | +| The name of an enum type the schema declares | The generated Java enum of that type | Matched without SQL identifier delimiters and case-insensitively, as PostgreSQL resolves an unquoted type name. See [Enum Types](#enum-types). | -Any spelling that is not listed above, and any array of any element type, has no -Java mapping. Such a column is recorded with its declared type instead of +Any spelling that is not listed above, and any array of any element type, +including an array of an enum type, has no Java mapping. Such a column is recorded with its declared type instead of failing the schema, and fails only a query that uses it; see [Unsupported Types and DDL](#unsupported-types-and-ddl). The generated repository imports `java.time.LocalDate`, `java.time.LocalTime`, `java.time.LocalDateTime`, `java.time.OffsetDateTime`, `java.math.BigDecimal`, -and `java.util.UUID` as needed; the remaining types need no import. Each result -column is read at its one-based position in the selected-column list, with -`resultSet.getObject(position, JavaType.class)`, or with -`resultSet.getString(position)` for a `JSON` or `JSONB` column, which the driver -reports as a type of its own rather than as a character type. +and `java.util.UUID` as needed; the remaining types need no import. A generated +enum belongs to the generated package, so it is used by its simple name and +needs no import either. Each result column is read at its one-based position in +the selected-column list, with `resultSet.getObject(position, JavaType.class)`, +or with `resultSet.getString(position)` for a `JSON` or `JSONB` column, which +the driver reports as a type of its own rather than as a character type. An +enum column reads its label the same way and resolves it with +`.fromLabel(...)`. A `JSON` or `JSONB` argument is passed to the executor as `new dev.sqlcj.runtime.UntypedText(value)`, written out in full so that the generated imports are unchanged, and `JdbcQueryExecutor` binds that text with `java.sql.Types.OTHER`. PostgreSQL then types the text from the context of its placeholder, which is what a `json` or `jsonb` column or comparison needs: text -bound as `varchar` is rejected there. +bound as `varchar` is rejected there. An enum argument is passed the same way, +around the label of its constant, because a label bound as `varchar` is +rejected where an enum is expected. + +## Enum Types + +`CREATE TYPE AS ENUM (...)` adds an enum type to the schema, and a +non-array column whose declared type names it is modeled as a column of that +type. Every query that reads or binds such a column uses the one Java enum the +package generates for the type: + +```sql +CREATE TYPE stage_setting AS ENUM ('indoor', 'outdoor'); + +ALTER TYPE stage_setting ADD VALUE 'covered' BEFORE 'outdoor'; +``` + +```java +// Code generated by sqlcj. DO NOT EDIT. + +package com.example.app.db; + +/** + * Generated by sqlcj. + * + * Enum: stage_setting + */ + +public enum StageSetting { + + INDOOR("indoor"), + COVERED("covered"), + OUTDOOR("outdoor"); + + private final String label; + + StageSetting(String label) { + this.label = label; + } + + public String label() { + return label; + } + + public static StageSetting fromLabel(String label) { + if (label == null) { + return null; + } + + for (StageSetting value : values()) { + if (value.label.equals(label)) { + return value; + } + } + + throw new IllegalArgumentException("Unknown label for enum type stage_setting: " + label); + } +} +``` + +- The constants keep their exact PostgreSQL labels and follow them in + PostgreSQL's own sort order, which is the declared order with each added + label in the position its `ALTER TYPE ... ADD VALUE` places it. +- A constant is named by the naming rules in + [Generated Java Names](configuration.md#generated-java-names). +- `fromLabel` reads back a label: `null` for a SQL `NULL`, and a failure for a + label the generated enum does not hold, which means the database declares one + the schema source does not. +- Only an enum type a query actually uses generates a file. +- An array of an enum type, such as `stage_setting[]`, has no Java mapping and + is recorded with its declared type like any other array. + +These statements update the enum types the statements and files before them +left: + +| Statement | Handling | +| --- | --- | +| `CREATE TYPE ... AS ENUM (...)` | Adds the type with its declared labels. | +| `ALTER TYPE ... ADD VALUE '