Identifier names, explained¶
Tables, columns, schemas and other database objects have names. In SQL, one name can
be written in many ways: orders, ORDERS, "ORDERS", [orders], `orders`.
Sometimes two spellings name the same object. Sometimes they name two different
objects. The answer depends on the database, on the kind of object, and sometimes on a
database setting.
GSP Java has one name service that knows these rules for every database that GSP supports. It answers four questions:
Are these two names the same object? Which key should I use for this name in a map? How does the database store this name? How do I write this name back into SQL?
This page explains the name service, IdentifierService, and the APIs that work with
it. Each section shows a small SQL example, the Java code, and the real output of that
code. The complete API reference is at the end of the page.
Who this page is for
You build your own application on GSP Java. Your code compares, stores, looks up or prints the names of tables, columns and other database objects. This page tells you which API to call, what it does, and what you need to know about database names to use it well. You do not need to know how GSP works inside.
Verified against
| GSP Java version | 4.2.10 (TBaseType.versionid), released 2026-09-24 |
| Page last verified | 2026-09-24 |
| Samples | 19 Java programs, each compiled and run against the 4.2.10 jar |
| Java versions | the samples give the same output on JDK 8, 17 and 21 |
Every block of program output on this page was copied from a real run of that build. None of it is written by hand. See Refreshing this page for how to bring the page forward to a newer release.
To run a sample, put the GSP jar on the class path (on Windows, use ; instead of :
between the entries). The lineage sample in section 9.4 also needs
the JAXB API jar (javax.xml.bind):
1 2 | |
1. What you need to know about names¶
1.1 Identifiers and qualified names¶
- An identifier is the name of a database object as it appears in SQL. Tables, views, columns, schemas, databases, functions, procedures and aliases all have identifiers. This page also says simply name.
- A qualified name has several parts with dots between them:
hr.emp(schema and table),sales.dbo.orders(database, schema and table),e.ename(table alias and column). Each part is called a segment. - A simple name has only one segment, for example
emp.
1.2 Unquoted and quoted names¶
You can write a name in two ways:
- Unquoted. The SQL standard calls this a regular identifier:
Store,order_date. It can contain only letters, digits and a few other characters such as_. In most databases it cannot be a reserved word such asDATE. - Quoted. The SQL standard calls this a delimited identifier. The name is written
between two delimiters, which are special quote characters:
"Order Date",[Order Date],`Order Date`. A quoted name can contain spaces, dots and other characters, and it can be a reserved word.
The delimiters are not part of the name. "Order Date" names an object called
Order Date. To put the closing delimiter inside a quoted name, you write it twice:
"a""b" is the name a"b, and [a]]b] is the name a]b. BigQuery uses a backslash
instead: `a\`b`.
Each database has its own delimiters. This table shows the delimiters that GSP 4.2.10
reads, and the delimiter that GSP writes when it adds one. The first column shows the
EDbVendor constants that you pass to GSP.
EDbVendor |
GSP reads | GSP writes |
|---|---|---|
dbvoracle, dbvdb2, dbvsnowflake, dbvredshift, dbvteradata, dbvpresto, dbvtrino, dbvvertica, dbvgreenplum, dbvgaussdb, dbvduckdb, dbvhana, dbvnetezza, dbvexasol, dbvfirebird, dbvdameng, dbvinformix, dbvopenedge, dbvansi, dbvgeneric, dbvodbc |
"x" |
"x" |
dbvpostgresql, dbvedb |
"x", U&"x" |
"x" |
dbvmssql, dbvazuresql |
[x], "x" |
[x] |
dbvaccess |
[x], "x" |
[x] |
dbvmysql, dbvoceanbase |
`x` (also "x" when ANSI_QUOTES is on, see 8.1) |
`x` |
dbvdoris, dbvstarrocks, dbvbigquery, dbvhive, dbvimpala, dbvflink, dbvcouchbase |
`x` |
`x` |
dbvsparksql, dbvdatabricks, dbvathena |
`x`, "x" |
`x` |
dbvclickhouse |
"x", `x` |
"x" |
dbvsqlite |
"x", [x], `x` |
"x" |
dbvsybase, dbvsybasease, dbvsybaseiq, dbvsqlanywhere |
"x", [x] |
"x" |
dbvdax |
table names 'x', column names [x] |
the same |
dbvmdx |
[x] |
[x] |
dbvpowerquery |
#"x" |
#"x" |
dbvsoql |
none: SOQL has no quoted names | the name without delimiters |
For a SQL Server or Azure SQL column, GSP also reads the old alias form 'x' (as in
SELECT a AS 'total') as the name x. GSP only reads this form. It never writes it.
1.3 Case folding¶
Many databases change the letters of an unquoted name to one case before they store it. This is called case folding. We say that the database folds the name to upper case or to lower case. A quoted name is usually stored exactly as it is written.
1 2 3 4 5 6 | |
- Oracle, DB2, Snowflake and the SQL standard fold unquoted names to upper case.
- PostgreSQL, Redshift, Hive and many others fold unquoted names to lower case.
- SQL Server, ClickHouse and BigQuery do not fold names, and MySQL does not fold column names. They keep the case that you wrote.
1.4 Case-sensitive, case-insensitive and collation¶
The second question is how a database compares two names:
- Case-sensitive:
Ordersandordersare two different names. ClickHouse and Flink work like this, and so do BigQuery table names. - Case-insensitive:
Ordersandordersare the same name. MySQL column names, Hive names and BigQuery column names work like this. - Collation-based: SQL Server and Azure SQL compare names with the database
collation. A collation is a set of rules for sorting and comparing text. The
default collation,
SQL_Latin1_General_CP1_CI_AS, is case-insensitive (CI). A collation such asLatin1_General_CS_ASis case-sensitive (CS).
In a database that folds names, the fold decides most answers. Oracle folds store and
STORE to STORE, so they are one name. "Store" stays Store, so it is a different
name.
1.5 The rules GSP uses¶
For each database, GSP keeps a rule with four parts: how an unquoted name folds, how
an unquoted name is compared, how a quoted name folds, and how a quoted name is
compared. The class IdentifierRules holds one rule. The class IdentifierProfile
holds all the rules of one database. You can print them:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
Read the first rule like this: in Oracle, an unquoted table name folds to upper case
(UPPER) and the comparison ignores case (INSENSITIVE). A quoted table name keeps its
case (NONE) and the comparison respects case (SENSITIVE).
These are the rules of every database in GSP 4.2.10, with the default settings described in section 8.1:
EDbVendor |
Unquoted name: fold / compare | Quoted name: fold / compare |
|---|---|---|
dbvoracle, dbvdb2, dbvsnowflake, dbvhana, dbvnetezza, dbvexasol, dbvfirebird, dbvdameng, dbvansi, dbvgeneric, dbvodbc |
UPPER / INSENSITIVE |
NONE / SENSITIVE |
dbvpostgresql, dbvgreenplum, dbvgaussdb, dbvedb, dbvinformix, dbvopenedge, dbvsoql |
LOWER / INSENSITIVE |
NONE / SENSITIVE |
dbvredshift |
LOWER / INSENSITIVE |
LOWER / INSENSITIVE |
dbvpresto, dbvtrino, dbvathena, dbvvertica |
LOWER / INSENSITIVE |
NONE / SAME_AS_UNQUOTED (that is, INSENSITIVE) |
dbvhive, dbvsparksql, dbvimpala, dbvdatabricks, dbvteradata, dbvduckdb, dbvsqlite, dbvsybase, dbvsybasease, dbvsybaseiq, dbvsqlanywhere, dbvaccess, dbvdax, dbvmdx |
LOWER / INSENSITIVE |
NONE / INSENSITIVE |
dbvmssql, dbvazuresql |
NONE / COLLATION_BASED |
NONE / COLLATION_BASED |
dbvmysql, dbvoceanbase, dbvdoris, dbvstarrocks: table, database and other names |
LOWER / INSENSITIVE |
NONE / INSENSITIVE |
dbvmysql, dbvoceanbase, dbvdoris, dbvstarrocks: column, function and procedure names |
NONE / INSENSITIVE |
NONE / INSENSITIVE |
dbvbigquery: table, dataset, project, function and procedure names |
NONE / SENSITIVE |
NONE / SENSITIVE |
dbvbigquery: column names |
NONE / INSENSITIVE |
NONE / INSENSITIVE |
dbvclickhouse, dbvcouchbase, dbvflink, dbvpowerquery |
NONE / SENSITIVE |
NONE / SENSITIVE |
Notes:
- Informix, OpenEdge and SOQL use the same rule as PostgreSQL.
- For Sybase (
dbvsybase,dbvsybasease), GSP assumes a server with a case-insensitive sort order. A server with a binary (case-sensitive) sort order needs your own rule (section 8.3). - Some rows depend on a database setting: MySQL
lower_case_table_names, the SQL Server collation, SnowflakeQUOTED_IDENTIFIERS_IGNORE_CASEand Redshiftenable_case_sensitive_identifier. Section 8 shows how to tell GSP the setting of your database.
1.6 Object types¶
Most name APIs take an ESQLDataObjectType value: the kind of object that the name
refers to. The kind matters because some databases use different rules for different
kinds of objects. A MySQL table name and a MySQL column name follow different rules, and
so do BigQuery table and column names. In DAX, even the delimiter depends on the kind of
object.
Pass the kind of the object that the name refers to, not the place where you found the
name. Compare a table alias as dotTable and a column alias as dotColumn. A view is
also a dotTable.
GSP puts the object types into three groups. By default, all types in one group use the same rule:
Group (IdentifierProfile.ObjectGroup) |
Object types (ESQLDataObjectType) |
|---|---|
NAME_GROUP |
dotCatalog (a database, or a BigQuery project), dotSchema, dotTable, dotTrigger, dotSynonyms, dotSequence, dotIndex, dotConstraint, dotServer, dotDblink, dotDataType, dotParameter, dotUnknown |
COLUMN_GROUP |
dotColumn |
ROUTINE_GROUP |
dotFunction, dotProcedure, dotOraclePackage, dotRoutine |
1.7 SQL text and stored names¶
A name reaches your program in one of two forms:
- SQL text. The name as it is written in a SQL statement, with its delimiters:
emp,"Order Items",[Order Items]. GSP calls this the lexical form,IdentifierInputForm.SQL_LEXICAL. - Stored name. The name as the database keeps it in its catalog, that is, in its
system tables such as Oracle
ALL_TABLESorINFORMATION_SCHEMA.COLUMNS. A stored name has no delimiters, and its case is already final:EMP,Order Items. GSP calls this formIdentifierInputForm.CATALOG_STORED.
The same text can mean two different names in the two forms. In Oracle, the SQL text
Emp means the stored name EMP. But the stored name Emp belongs to an object that
you can only write as "Emp" in SQL. Always know which form you have.
Section 10 and section 11 show the APIs that take the
form as a parameter.
1.8 Display name and lookup spelling¶
A stored name is also the best display name, the text that you show to people.
Show Order Date, not [Order Date]. This is what the database itself shows in its
catalog, and GSP does not add delimiters to a display name.
To write a name back into SQL, you need a lookup spelling: SQL text that finds the
object again. Order Date needs delimiters: "Order Date" in Oracle, [Order Date] in
SQL Server. An Oracle column stored as Amount needs them too. Without delimiters,
Amount folds to AMOUNT, and that is a different column.
Keep the display name and the lookup spelling as two different strings. Section 6 and section 7 show the APIs.
2. Which API do I need?¶
| I want to ... | Use | Section |
|---|---|---|
| know if two simple names name the same object | SQLUtil.sameName |
3 |
compare names that can have dots (hr.emp) |
SQLUtil.compareIdentifier |
4 |
use a name as a key in a HashMap or a HashSet |
SQLUtil.canonKey, which returns a CanonKey |
5.1 |
| use a qualified name as a key | SQLUtil.qualifiedCanonKeyFromLexical, IdentifierService.qualifiedCanonKeyFromStored |
4.2 |
| remove duplicate names, or keep a set of names | NameService.distinctCopy, NameService.createSet |
5.2 |
| get a name without delimiters and with the case folded | IdentifierService.normalizeStatic |
6.1 |
| check, remove or add delimiters | IdentifierCodec.isQuoted, decodeLexical, encodeStored |
6.2 |
| write correct SQL text from stored names | SqlNameGenerator.generate |
7 |
describe database settings such as the collation or lower_case_table_names |
IdentifierProfile, IdentifierProfile.VendorFlags, IdentifierProfile.Builder |
8 |
| use those settings in a parse or a lineage run | TGSqlParser.setIdentifierProfile, Option.setIdentifierProfile |
9 |
| save the identity of a name in a file or a database, or share it with other programs | IdentifierService.persistentKeyV1 |
10 |
| match names from SQL with my own catalog | IdentifierService.canonKeyOf, TSQLEnv.toLexical |
11 |
SQLUtil.sameName, SQLUtil.compareIdentifier and SQLUtil.canonKey use the
default rules of each database. When your database uses other settings, build an
IdentifierProfile (section 8) and use the service of that profile.
3. Are two names the same? SQLUtil.sameName¶
SQLUtil.sameName(vendor, objectType, name1, name2) answers one question: do two simple
names name the same object in this database? Pass each name exactly as it is written in
the SQL text, with its delimiters. GSP reads the delimiters itself.
1 2 3 4 | |
The sample asks the same kind of question for several databases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
How GSP decides. For each name, GSP removes the delimiters and then applies the
rule for that name's own quote state: it folds the name, or it ignores the case, or it
uses the collation. The result is the canonical form of the name: one standard form
that all spellings of the same name share. Two names are the same when their canonical
forms are equal. Because every name goes through the same steps, the answers always
agree with each other: when a equals b and b equals c, then a equals c. This
makes the answers safe for maps and sets (section 5).
Things to know:
- Pass one segment in each argument. For names with dots, use
compareIdentifier(section 4). - Two
nullnames are equal. Anullname and a name that is notnullare not equal. The vendor must not benull: GSP throws aNullPointerException. sameNamealways uses the default rules of the vendor.IdentifierService.areEqualStatic(vendor, objectType, name1, name2)is the same method.
4. Qualified names¶
4.1 SQLUtil.compareIdentifier¶
SQLUtil.compareIdentifier(vendor, objectType, name1, name2) compares names that can
have dots. It splits both names into segments and checks that they have the same number
of segments. Then it compares each segment with the rule of its own kind: a schema
segment with the schema rule, a column segment with the column rule, and so on.
1 2 3 4 | |
objectType is the kind of the whole name. GSP finds the kind of each segment from
the number of segments:
objectType |
1 segment | 2 segments | 3 segments | 4 segments |
|---|---|---|---|---|
dotTable, and also dotFunction, dotProcedure, dotOraclePackage, dotTrigger, dotSynonyms |
table | schema.table | database.schema.table | |
dotColumn |
column | table.column | schema.table.column | database.schema.table.column |
dotSchema |
schema | database.schema |
MySQL, Teradata, Hive and Impala have no schema level. For them, two segments of a table
name are database.table, and three segments of a column name are
database.table.column.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
1 2 3 4 5 6 7 8 9 10 11 | |
What the output shows:
- Each segment keeps its own quote state.
"HR"."EMP"ishr.emp, but"hr"."emp"is not. hr.empandempare different. GSP never guesses a missing schema. If you know the default schema of the session, add it yourself (section 11).- In SQL Server,
Sales..Ordershas an empty middle segment. GSP does not replace it withdbo, because the real default schema depends on the user. "A.B"is one name that contains a dot. It is not the qualified namea.b.sameNamegives a wrong answer for a qualified name. When a name can have dots, always usecompareIdentifier.
4.2 Keys for qualified names, and some limits¶
SQLUtil.qualifiedCanonKeyFromLexical(vendor, objectType, name) returns a
QualifiedCanonKey. This is a key for a qualified name in SQL text that you can use in
a HashMap or a HashSet. It holds one CanonKey (section 5) for each
segment, and the kind of the whole name. For a name in the stored form, call
qualifiedCanonKeyFromStored(storedSegments, objectType) on an IdentifierService, or
the static IdentifierService.qualifiedCanonKeyFromStoredStatic(vendor, objectType,
storedSegments), with one list entry for each segment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | |
1 2 3 4 5 6 7 8 | |
- The map finds
"HR"."EMP", because it is the same table ashr.emp. It does not findhr."Emp", which is another table. getLeafKey()returns the key of the last segment, here the columnENAME.- A BigQuery path in one pair of back-ticks. BigQuery allows a whole path in one
pair of back-ticks:
`myproj.sales.Orders`.compareIdentifierdoes not split such a path, so it answersfalse. The qualified key splits it correctly. For BigQuery, compare qualified names by their keys. - Object types without a name hierarchy.
compareIdentifierand the qualified keys know the name hierarchy of these object types only:dotCatalog,dotSchema,dotTable,dotColumn,dotFunction,dotProcedure,dotOraclePackage,dotTriggeranddotSynonyms. For every other type, for exampledotSequence,dotIndex,dotConstraintordotRoutine,compareIdentifierreturnsfalseeven for two equal names, andqualifiedCanonKeyFromLexicalthrows anUnsupportedOperationException. When your code can get any object type, check first withQualifiedNameHierarchy.hasHierarchyMapping(vendor, objectType). For a simple name of such a type, usesameName. - A
QualifiedCanonKeynever fills in a missing segment. Keys with a different number of segments are never equal.
5. Keys for maps and sets¶
5.1 CanonKey¶
To put names into a HashMap or a HashSet, you need a key that is equal exactly when
the names are the same object. SQLUtil.canonKey(vendor, objectType, name) returns such
a key, a CanonKey. Two keys are equal exactly when sameName returns true for the
two names.
Do not build keys yourself with toUpperCase() or toLowerCase(), or by removing the
delimiters. This PostgreSQL script creates two tables, and a hand-made key mixes them
up:
1 2 3 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | |
1 2 3 4 5 6 7 8 9 | |
What the output shows:
- The two hand-made keys are equal, so a map keeps only one of the two tables.
- With
CanonKey, the map has two entries.STOREfinds the tablestore, because the unquotedSTOREfolds tostore."STORE"finds nothing, because no table is stored asSTORE. - Case mapping in Java depends on the locale.
String.toUpperCase()without an argument uses the default locale of the Java virtual machine, and in a Turkish localetitlebecomesTİTLE. A key built withtoUpperCase()can therefore change when the same program runs on another computer. The name service does not depend on the locale or on the JDK version. IdentifierService.keyForMapreturns text, not identity. In SQL Server, and in every rule that ignores case without folding (MySQL column names, for example), one name can give two different texts. UsecanonKey.
Rules for CanonKey:
- A
CanonKeyis for use inside one running program only. Do not save it in a file or a database, and do not send it to another program. For that, use a persistent key (section 10). - A
CanonKeyrecords the vendor and the object group, but not the object type inside the group. A table key and a schema key with the same text can be equal (see the last line of the output in section 5.2). Use one map for each kind of object, or use aQualifiedCanonKey, which also records the object type.
5.2 Sets and lists of names: NameService¶
NameService has ready-made helpers that use the same rules as canonKey.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
1 2 3 4 5 6 | |
distinctCopyremoves duplicates and keeps the first spelling of each name.createSetreturns aSet<String>whoseadd,containsandremoveuse the rules of the database. It does not acceptnull.indexOfworks likeList.indexOf, with the rules of the database.new NameService(vendor)uses the default rules.NameService.forIdentifierService(service)uses the rules of any service, for example a service built from a profile (section 8).
6. Stored form, delimiters and display¶
6.1 The normalized form: normalizeStatic¶
IdentifierService.normalizeStatic(vendor, objectType, name) removes the delimiters
from one segment and applies the fold of the name's quote state. For most databases,
the result is the name as the database stores it, so it is also a good display name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1 2 3 4 5 6 7 8 9 10 | |
- The normalized form is not a key. The same object can give two different texts: the
MySQL column
AmountgivesAmount, andAMOUNTgivesAMOUNT. UsecanonKeyfor keys. normalizeStaticworks on one segment. For a qualified name, callnormalizeQualifiedName(name, objectType)on a service, for example onIdentifierService.defaultServiceFor(vendor).
6.2 Delimiters: IdentifierCodec¶
1 2 | |
IdentifierCodec knows the delimiters of each database and how to escape them. It only
adds and removes delimiters. It never changes the case of a name: folding is the job of
normalize. Every method takes an IdentifierProfile and an object type. Get the
default profile of a database with
IdentifierProfile.forVendor(vendor, IdentifierProfile.VendorFlags.defaults()).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
1 2 3 4 5 6 7 8 9 10 11 | |
| Method | What it does |
|---|---|
isQuoted(profile, objectType, text) |
true when the text starts with a delimiter that this database reads for this kind of object. With the default MySQL settings, "Amount" is a string, not a name, so the answer is false. |
decodeLexical(profile, objectType, sqlText) |
SQL text to stored name. It removes the delimiters and resolves escaped characters. Unquoted text comes back unchanged (not folded). Broken text such as [a.b[ throws IdentifierCodec.MalformedIdentifierException. |
encodeStored(profile, objectType, storedName) |
Stored name to SQL text. It always adds delimiters, even when the name does not need them. (SOQL has no delimiters, so there the name comes back unchanged.) |
isVendorValidSpelling(profile, objectType, sqlText) |
true when the characters and the delimiters are valid for this database. It does not check reserved words or length limits. |
hasQuotedForm(profile, objectType) |
false only for a database without delimiters (SOQL). |
requiresDelimiters(profile, objectType) |
true only when a name must always be delimited: DAX column names such as [Amount]. |
The last three lines of the output show the display name and the lookup spelling of an
Oracle column created as "Amount" (section 1.8). The display name
Amount, written without delimiters, does not find the column. The lookup spelling
"Amount" finds it.
6.3 Display names in GSP reports: DisplayNameMode¶
GSP has two reports that list the tables and columns of a query: TGetTableColumn, and
TSQLResolver2ResultFormatter, which prints the result of the resolver
(section 9). Both can print names in three modes
(gudusoft.gsqlparser.resolver2.format.DisplayNameMode):
| Mode | What it prints |
|---|---|
DISPLAY |
the name without delimiters, in the case that was written |
SQL_RENDER |
valid SQL text for the name, with delimiters only where they are needed (section 7) |
CANONICAL |
the folded form: in Oracle, unquoted names in upper case; quoted names keep their case |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
1 2 3 4 5 6 7 | |
1 2 3 4 5 6 7 | |
1 2 3 4 5 6 7 | |
TGetTableColumnusesSQL_RENDERby default.setQuotePolicy(SqlNameGenerator.QuotePolicy)chooses how many delimitersSQL_RENDERwrites.TSQLResolver2ResultFormatterhas the samesetDisplayNameModeandsetQuotePolicymethods. Its default comes fromTSQLResolverConfig.getDisplayNameMode(), which isDISPLAYunless you change it withTSQLResolverConfig.setDisplayNameMode(DisplayNameMode).- When one object is written in several ways,
DisplayNamePolicydecides which spelling a report prints:PREFER_DEFINITION_SITE(the default),PREFER_FIRST_OCCURRENCEorPREFER_METADATA.
7. Write names back into SQL: SqlNameGenerator¶
SqlNameGenerator writes SQL text from stored names. You give it one Segment for
each part of the name: the stored name and its kind (Segment.catalog,
Segment.schema, Segment.table, Segment.column, and so on). It returns a Result:
either the SQL text, or the reason why it cannot write one.
There are two quote policies (SqlNameGenerator.QuotePolicy):
ALWAYSdelimits every segment. This is always correct.generate(vendor, segments...)without a policy usesALWAYS.WHEN_NEEDEDwrites a segment without delimiters only when GSP can prove that the bare word names the same object. Otherwise it delimits the segment.
GSP writes a segment bare only when all of these are true:
- The kind of object has a bare form. A DAX column is always written
[Name]. - The stored name does not start like a quoted name. (A SQL Server table stored as
[x], with the brackets as part of its name, is written[[x]]].) - The characters are valid for an unquoted name.
- The bare word names the same object as the delimited spelling. (In Oracle, a bare
Amountfolds toAMOUNT.) - The word is not in GSP's keyword list for the database.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
- The Oracle column stored as
Amountmust be delimited. The column stored asAMOUNTcan stay bare. DATEis a keyword, so GSP delimits it.- The SQL Server columns
*,1anda.bare displayed bare (section 1.8), but written as[*],[1]and[a.b]. - When GSP cannot prove that a result is correct, it fails with a
SqlNameGenerator.Failurevalue:NO_SEGMENTS,NULL_SEGMENT,EMPTY_SEGMENT(GSP never fills in a missing part),NO_QUOTED_FORM,NOT_REPRESENTABLEorROUND_TRIP_MISMATCH.getDetail()explains the failure.getSqlOrThrow()throws anIllegalStateExceptioninstead of returning a failure. respell(vendor, policy, objectType, sqlText)rewrites a name that you read from SQL text. WithWHEN_NEEDED, a name written without delimiters stays as it was written: GSP does not change its case, because SQL text does not show the stored case. A delimited name is decoded and then written like a stored name.SqlNameGeneratoralways uses the default rules of the database. It has no profile parameter.
8. Describe your database settings: IdentifierProfile¶
The rules in section 1.5 assume the default settings of each database.
Some databases have settings that change the rules. When your database uses other
settings, describe them in an IdentifierProfile, and compare names with a service
built from that profile.
8.1 VendorFlags¶
IdentifierProfile.VendorFlags holds five settings. Each setting is used by only some
databases:
| Field | Default | Used by | Meaning |
|---|---|---|---|
mysqlLowerCaseTableNames |
1 |
dbvmysql, dbvoceanbase |
The server variable lower_case_table_names. 0: table and database names are case-sensitive. 1: they are stored in lower case and compared case-insensitively. 2: they are stored as written and compared case-insensitively. Column names are always case-insensitive. |
defaultCollation |
"SQL_Latin1_General_CP1_CI_AS" |
dbvmssql, dbvazuresql |
The collation. A name that contains _CS_ (or ends with _CS) makes names case-sensitive. See section 8.2. |
redshiftEnableCaseSensitive |
false |
dbvredshift |
The setting enable_case_sensitive_identifier. |
snowflakeQuotedIdentifiersIgnoreCase |
false |
dbvsnowflake |
The parameter QUOTED_IDENTIFIERS_IGNORE_CASE. With true, quoted names also fold to upper case and are compared case-insensitively. |
mysqlAnsiQuotes |
false |
dbvmysql, dbvoceanbase |
The sql_mode option ANSI_QUOTES. With true, "x" is a quoted name, not a string. It changes the delimiters only, not the case rules. |
Doris and StarRocks do not use mysqlLowerCaseTableNames or mysqlAnsiQuotes.
Create the flags with new VendorFlags(mysqlLowerCaseTableNames, defaultCollation,
redshiftEnableCaseSensitive, snowflakeQuotedIdentifiersIgnoreCase). A fifth argument
sets mysqlAnsiQuotes; without it, mysqlAnsiQuotes is false. One VendorFlags object
holds the settings of all databases, so the simplest way is to copy the values of
VendorFlags.defaults() and change only the one you need, as the sample does. Then
create the profile with IdentifierProfile.forVendor(vendor, flags), and a service for
it with IdentifierService.forProfile(profile).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
1 2 3 4 5 6 7 8 9 | |
default=is the answer ofIdentifierService.defaultServiceFor(vendor), the service with the default rules.SQLUtiland the static methods always use this service.configured=is the answer ofIdentifierService.forProfile(profile).- Redshift: with
redshiftEnableCaseSensitive = true, GSP 4.2.10 compares all names case-sensitively, unquoted names too:Ordersandordersare two names. If you need unquoted names to fold to lower case while quoted names are case-sensitive, write that rule yourself (section 8.3).
8.2 SQL Server collations¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
1 2 3 4 5 6 | |
- A collation with
_CI_in its name is case-insensitive. A collation with_CS_is case-sensitive. - A collation name without
_CIor_CS, such asLatin1_General_BIN2, compares names case-sensitively. - GSP does not model accent-insensitive collations (
_AI).resumeandrésuméstay two different names, even underLatin1_General_CI_AI. - Always pass a real collation name. In 4.2.10, an empty or
nullcollation makes SQL Server names case-sensitive, and that is not the SQL Server default.
8.3 Your own rules: IdentifierRules and the profile builder¶
When no flag describes your database, write the rule yourself.
new IdentifierRules(unquotedFold, unquotedCompare, quotedFold, quotedCompare) takes:
IdentifierRules.CaseFold:NONE(keep the case),UPPERorLOWER.IdentifierRules.CaseCompare:SENSITIVE,INSENSITIVE,COLLATION_BASED(compare with the collation; SQL Server and Azure SQL), orSAME_AS_UNQUOTED(for the quoted part only: compare quoted names like unquoted names).
Then start a builder from the default rules of the database with
IdentifierProfile.builder(vendor) or IdentifierProfile.builder(vendor, flags), and
replace what you need:
| Builder method | What it replaces |
|---|---|
withObjectRules(objectType, rules) |
the rule of one object type, for example only dotTable |
withNameRules(rules), withColumnRules(rules), withRoutineRules(rules) |
the rule of one group: NAME_GROUP, COLUMN_GROUP or ROUTINE_GROUP |
withRules(group, rules) |
the rule of one group |
withFlags(flags) |
the flags. GSP computes the default rules again from the new flags and keeps your replacements. |
withoutObjectRules(objectType), withoutRules(group) |
removes a replacement that you made before |
A rule for one object type wins over a rule for its group. A rule for a group wins over
the default rule. build() returns a profile that cannot change. profile.toBuilder()
starts a new builder from an existing profile. The last two lines of the sample in
section 8.1 make BigQuery table names case-insensitive, while schema
names keep the default rule.
For example, a Sybase ASE server with a binary (case-sensitive) sort order needs NONE /
SENSITIVE rules for the groups that you use, because GSP's default Sybase rule is
case-insensitive.
A profile states your assumption about the database. GSP does not read these
settings from a live server. A statement in the script does not change the profile
either: for example, BigQuery CREATE SCHEMA ... OPTIONS(is_case_insensitive=...) is
parsed, but it does not change the rules.
9. Use a profile for a whole analysis¶
A profile changes the answers only where you give it to GSP. There are four places. Use one profile for one analysis. When two places get profiles with different rules, GSP throws an exception instead of choosing one of them.
Two terms used in this section: the resolver is the part of the parser that finds
the table and the column of each column reference, and resolver2 is the name of its
current version. Data lineage tells which source columns feed each target column;
DataFlowAnalyzer computes it.
| Where | API | Use it when |
|---|---|---|
| parser | TGSqlParser.setIdentifierProfile(profile) |
you parse SQL with TGSqlParser |
| catalog | new TSQLEnv(vendor, profile) |
you give GSP the tables and columns of your database with TGSqlParser.setSqlEnv |
| resolver2 | TSQLResolverConfig.createForProfile(profile) or setIdentifierProfile(profile) |
you pass your own resolver configuration with TGSqlParser.setResolver2Config |
| lineage | Option.setIdentifierProfile(profile) |
you run DataFlowAnalyzer |
9.1 Parser: TGSqlParser.setIdentifierProfile¶
1 2 3 | |
With the default collation, AMOUNT is the column Amount. With a case-sensitive
collation, the table has no column AMOUNT, and SQL Server rejects the query.
The sample reads the answer of the resolver. getResolution().getStatus() is
EXACT_MATCH when the resolver found exactly one column, and NOT_FOUND when no table
in the query has the column.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
The program prints both runs:
1 2 3 4 | |
1 2 3 4 | |
Rules:
- Call
setIdentifierProfilebeforeparse(). GSP fixes the profile when the parse starts. A call afterparse()throws anIllegalStateException.prepareForReuse()resets the parser, and after that you can set a profile again. - The profile must be for the same vendor as the parser. Otherwise GSP throws an
IllegalArgumentException. - After
parse(),parser.getSqlEnv().getIdentifierService()is the service that the parse used. Use it when your own code must compare names in the same way. TObjectName.getNormalizedTableString()and the othergetNormalized...String()methods always use the default rules, not your profile. With a profile, normalize withparser.getSqlEnv().getIdentifierService().normalize(name, objectType).
9.2 Your own catalog: TSQLEnv¶
Create your TSQLEnv with the profile: new TSQLEnv(vendor, profile). The environment
keeps this profile for its whole life, because it builds its indexes with it. A parser
that gets the environment with setSqlEnv uses the profile of the environment, so you do
not need setIdentifierProfile too. If you call both, the two profiles must have the
same rules. Otherwise parse() throws an IllegalStateException.
- Compare names under the profile of the environment with
env.getIdentifierService(). In 4.2.10, the instance methodscompareIdentifier(objectType, name1, name2),compareTableandcompareColumnofTSQLEnvuse the default rules of the vendor, not the profile of the environment. - When you load stored names into a
TSQLEnv, convert them first (section 11.2).
9.3 resolver2 configuration¶
Usually you do nothing here: parse() gives the profile of the parser to resolver2. You need to act only when you
pass your own TSQLResolverConfig with setResolver2Config. Then leave its profile
unset, or set the same profile. A profile with different rules makes parse() throw an
IllegalStateException.
9.4 Lineage: Option.setIdentifierProfile¶
Option holds the settings of a DataFlowAnalyzer run.
1 2 | |
With the default collation, Amount and AMOUNT are one column of src. With a
case-sensitive collation, they are two columns.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | |
The program prints both runs:
1 2 3 4 5 | |
1 2 3 4 5 | |
- Set the profile before
generateDataFlow(). The whole run uses the profile that the option had when the run started. - The profile must be for the same vendor as the option (
Option.setVendor). Otherwise the run fails with anIllegalStateException.
9.5 Azure SQL¶
GSP parses Azure SQL with its SQL Server parser: a TGSqlParser created for
dbvazuresql works as dbvmssql. For this reason:
- Parser: build the profile for
EDbVendor.dbvmssql. Adbvazuresqlprofile is refused with anIllegalArgumentException. - Lineage: in 4.2.10, an
Optionwith the vendordbvazuresqlaccepts no profile. If you need one, run the analysis withEDbVendor.dbvmssqland adbvmssqlprofile.
9.6 What a profile does not change¶
SQLUtil.sameName,SQLUtil.compareIdentifier,SQLUtil.canonKey, the staticIdentifierServicemethods,new NameService(vendor)andSqlNameGeneratoralways use the default rules of the vendor.- A profile changes only the analysis that you give it to. Two analyses can run at the same time with different profiles.
10. Save the identity of a name: persistent keys¶
A CanonKey is valid only inside one running program. When you must save the identity
of a name, for example in a database table, in a file, or in a cache that several
programs share, use a persistent key. The method
persistentKeyV1(objectType, name, form) of an IdentifierService returns a
PersistentIdentifierKey. As long as the rule stays the same, the same name gives the
same key in every Java version, on every operating system and in every locale.
A persistent key has three parts. Save all three, and compare all three:
getPolicyId(): an ID of the rules that made the key, for examplepk1-ry85qY4x....getGroup(): the object group.getPayload(): the canonical text of the name.
The third parameter says where the name came from: IdentifierInputForm.SQL_LEXICAL
for SQL text, or IdentifierInputForm.CATALOG_STORED for a name read from the catalog
of the database (section 1.7).
1 2 3 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
1 2 3 4 5 6 7 8 9 10 | |
What the output shows:
- The SQL text
empand the stored nameEMPgive equal keys. The SQL text"Emp"and the stored nameEmpgive equal keys. The SQL textEmp, which folds toEMP, and the stored nameEmpdo not. new PersistentIdentifierKey(policyId, group, payload)rebuilds a saved key.- In SQL text, a broken quoted name throws
IdentifierCodec.MalformedIdentifierException. The persistent API never guesses. - SQL Server and Azure SQL throw an
UnsupportedOperationException, because a collation comparison cannot be saved as portable text. Only a collation with_CS_in its name gives persistent keys. A custom profile whose unquoted and quoted parts mix a collation with a fold throwsIdentifierService.MixedKindProfileException, which is also anUnsupportedOperationException. approximateKeyV1(objectType, name, form)returns anApproximateIdentifierKeyfor every rule, SQL Server too. It removes the delimiters and ignores case. It is not an identity: in Oracle it gives the same key to"Emp"andEMP, which are two tables. Use it only to group or sort names, never to decide that two names are the same object.describePolicyV1(objectType)returns a readable description for logs. Do not use its text as a key.
A newer GSP version can change a policy ID, for example when it corrects the rule of a database. Never compare the payloads of keys with different policy IDs. When you load a saved key whose policy ID is not the current one for that vendor and object group, make a new key from the original name.
11. From SQL to your catalog: end-to-end examples¶
11.1 Match table names from the AST with your own list¶
Your application often has its own list of tables, read from the catalog of the
database. This example parses Oracle SQL, reads each table name from the AST (the tree
of objects that TGSqlParser builds from the SQL text), and looks it up in such a
list. It builds the keys one segment at a time with
IdentifierService.canonKeyOf(text, objectType, form): stored names from the catalog
with CATALOG_STORED, and names from the AST with SQL_LEXICAL. When the SQL has no
schema, the example uses the default schema of the session, which you must know.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
1 2 3 4 5 | |
TObjectName.getSchemaString()andgetTableString()return each segment as SQL text, with its delimiters. Pass them to the name service as they are.emphas no schema, so the example usesHR, the default schema of the session.hr."Emp"is not in the catalog: the quoted nameEmpis not the tableEMP.Sales.EmpfindsSALES.EMP, notHR.EMP: the schema segment decides.
11.2 Give your catalog to GSP: TSQLEnv.toLexical¶
You can also give your catalog to GSP, so that the resolver finds the columns for you.
TSQLEnv.addTable and TSQLTable.addColumn read every name as SQL text. So convert
a stored name first, or GSP folds it: in Oracle, the stored column Amount would become
AMOUNT. The TSQLEnv method toLexical(objectType, name,
IdentifierInputForm.CATALOG_STORED) converts a stored name into SQL text with the rules
of the environment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
1 2 3 4 | |
1 2 3 4 | |
Without the conversion, both answers about the column Amount are wrong: the legal
reference "Amount" is not found, and amount, which Oracle rejects, is found. With
toLexical, both answers are right. The static
TSQLEnv.toLexical(vendor, objectType, name, form) does the same with the default rules
of the vendor.
12. Common mistakes¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | |
1 2 3 4 5 6 7 8 | |
A checklist:
- Do not compare names with
equalsorequalsIgnoreCase. UsesameNameorcompareIdentifier. - Do not remove delimiters or change the case before you call GSP. Pass the text exactly as it was written: the quote state is part of the name.
- Do not use
sameNamefor names with dots. UsecompareIdentifier. - Do not use
normalize,keyForMapor the legacy "normal name" methods (SQLUtil.getIdentifierNormalName,getIdentifierNormalTableName,getIdentifierNormalColumnName, and the deprecatedSQLUtil.normalizeIdentifier) as keys. Two different names can get the same text, and one name can get two texts. UsecanonKey. - Do not save a
CanonKey. UsepersistentKeyV1. - Know the form of each name: SQL text (
SQL_LEXICAL) or stored name (CATALOG_STORED). - Pass the right object type: the kind of object that the name refers to.
- When your database does not use the default settings, use the profile everywhere: in
your own comparisons (
IdentifierService.forProfile(profile)) and in the analysis (section 9). - For SQL Server, pass a real collation name, never
nullor an empty string. - If your code uses an empty string to mean "no name", check for it before you compare
names: an empty string and the quoted empty name
""are equal.
13. API reference¶
All classes are in the package gudusoft.gsqlparser.sqlenv, unless another package is
given. Only public API is listed.
13.1 SQLUtil (gudusoft.gsqlparser.util)¶
| Method | Returns | Description |
|---|---|---|
sameName(EDbVendor dbVendor, ESQLDataObjectType objectType, String ident1, String ident2) |
boolean |
Whether two single-segment names name the same object, under the default rules of the vendor. Two null names are equal. dbVendor must not be null. |
compareIdentifier(EDbVendor dbVendor, ESQLDataObjectType sqlDataObjectType, String identifier1, String identifier2) |
boolean |
Compares names that can have dots, segment by segment (section 4.1). false when the numbers of segments differ, when a name is null, and for object types without a name hierarchy. |
canonKey(EDbVendor dbVendor, ESQLDataObjectType objectType, String identifier) |
CanonKey |
In-memory key of one segment. Key equality is sameName. null for a null name. |
qualifiedCanonKeyFromLexical(EDbVendor dbVendor, ESQLDataObjectType objectType, String qualifiedName) |
QualifiedCanonKey |
In-memory key of a qualified name in SQL text. null for a null or empty name. Throws UnsupportedOperationException for an object type without a name hierarchy. |
parseNames(String nameString) |
List<String> |
Splits a qualified name at the dots. A delimited segment stays whole, with its delimiters. |
parseNames(String nameString, EDbVendor vendor) |
List<String> |
The same, with the rules of a vendor. With dbvbigquery, a path in one pair of back-ticks is split into its parts. |
Legacy methods: getIdentifierNormalName, getIdentifierNormalTableName,
getIdentifierNormalColumnName and the deprecated normalizeIdentifier return a legacy
display text. They are not an identity (section 12).
13.2 IdentifierService¶
Static methods. They use the default rules of the vendor.
| Method | Returns | Description |
|---|---|---|
defaultServiceFor(EDbVendor dbVendor) |
IdentifierService |
The service with the default rules of the vendor. Never null. |
forProfile(IdentifierProfile profile) |
IdentifierService |
A service for your profile. It adds the collator (the Java object that compares text by a collation) that SQL Server and Azure SQL need. |
areEqualStatic(EDbVendor dbVendor, ESQLDataObjectType objectType, String ident1, String ident2) |
boolean |
The same as SQLUtil.sameName. |
canonKeyStatic(EDbVendor dbVendor, ESQLDataObjectType objectType, String identifier) |
CanonKey |
The same as SQLUtil.canonKey. |
normalizeStatic(EDbVendor dbVendor, ESQLDataObjectType objectType, String identifier) |
String |
The normalized form of one segment (section 6.1). null and "" come back unchanged. |
qualifiedCanonKeyFromLexicalStatic(EDbVendor dbVendor, ESQLDataObjectType objectType, String qualifiedName) |
QualifiedCanonKey |
The same as SQLUtil.qualifiedCanonKeyFromLexical. |
qualifiedCanonKeyFromStoredStatic(EDbVendor dbVendor, ESQLDataObjectType objectType, List<String> storedSegments) |
QualifiedCanonKey |
Key of a qualified name given as stored segments, outermost first. |
collatorProviderFor(EDbVendor vendor) |
CollatorProvider |
A new collator provider for dbvmssql and dbvazuresql, otherwise null. You need it only with the constructor. |
Constructor. IdentifierService(IdentifierProfile profile, CollatorProvider collatorProvider).
Prefer forProfile(profile), which chooses the collator provider for you.
Instance methods. They use the profile of the service. Note the order of the parameters: the comparison methods take the names first and the object type last, and the persistent-key methods take the object type first.
| Method | Returns | Description |
|---|---|---|
areEqual(String ident1, String ident2, ESQLDataObjectType objectType) |
boolean |
Whether two single-segment names name the same object. |
canonKey(String identifier, ESQLDataObjectType objectType) |
CanonKey |
In-memory key of one segment in SQL text. null for a null name. |
canonKeyOf(String text, ESQLDataObjectType objectType, IdentifierInputForm form) |
CanonKey |
In-memory key of one segment in the stated form. null for null text. Throws IllegalArgumentException when objectType or form is null. |
normalize(String identifier, ESQLDataObjectType objectType) |
String |
The normalized form of one segment. |
normalizeSegment(String segment, ESQLDataObjectType objectType) |
String |
The same as normalize. |
normalizeQualifiedName(String qualifiedName, ESQLDataObjectType objectType) |
String |
Normalizes each segment of a qualified name and joins them with dots. For display only. For SQL Server, an empty middle segment (db..t) becomes dbo. |
keyForMap(String identifier, ESQLDataObjectType objectType) |
String |
A text key of one segment. Not an identity for SQL Server or for rules that ignore case without folding; use canonKey. |
parseQualifiedName(String qualifiedName) |
List<String> |
Splits a qualified name with the rules of the vendor of the service. |
expandVendorSpecific(List<String> segments, EDbVendor vendor) |
List<String> |
For SQL Server and Azure SQL, replaces an empty middle segment with dbo. Other vendors: the list comes back unchanged. |
expandVendorSpecific(List<String> segments, EDbVendor vendor, String defaultSchema) |
List<String> |
The same, with your default schema instead of dbo. |
qualifiedCanonKeyFromLexical(String qualifiedName, ESQLDataObjectType objectType) |
QualifiedCanonKey |
Key of a qualified name in SQL text. null for null or "". Throws UnsupportedOperationException for an object type without a name hierarchy. |
qualifiedCanonKeyFromStored(List<String> storedSegments, ESQLDataObjectType objectType) |
QualifiedCanonKey |
Key of a qualified name given as stored segments, outermost first. A stored segment can contain a dot. |
persistentKeyV1(ESQLDataObjectType objectType, String name, IdentifierInputForm form) |
PersistentIdentifierKey |
Key of one segment that you can save (section 10). null for a null name. Throws IdentifierCodec.MalformedIdentifierException for broken SQL text, and UnsupportedOperationException for collation rules. |
approximateKeyV1(ESQLDataObjectType objectType, String name, IdentifierInputForm form) |
ApproximateIdentifierKey |
A key that works for every rule: it removes the delimiters and ignores case. Not an identity. |
describePolicyV1(ESQLDataObjectType objectType) |
String |
A readable description of the persistent and approximate policies, for logs. |
getProfile() |
IdentifierProfile |
The profile of the service. |
getCollatorProvider() |
CollatorProvider |
The collator provider, or null. |
Other public methods, buildCompositeKey(...), canUseCompositeKey(String),
keysForHierarchy(String, List<ESQLDataObjectType>) and
assertSingleSegmentOrThrow(String, ESQLDataObjectType), are not for general use.
buildCompositeKey joins the segments as they are, without the name rules, so its
result is not an identity key. Use QualifiedCanonKey instead.
Nested class. IdentifierService.MixedKindProfileException extends
UnsupportedOperationException. getUnquotedKind() and getQuotedKind() return the
IdentifierRules.CaseCompare of the two parts of the rule.
13.3 IdentifierProfile¶
An IdentifierProfile holds the rules of one database and cannot change after it is
built.
| Method | Returns | Description |
|---|---|---|
forVendor(EDbVendor vendor, VendorFlags flags) (static) |
IdentifierProfile |
The default rules of the vendor for these flags. |
forVendorOr(IdentifierProfile configured, EDbVendor vendor) (static) |
IdentifierProfile |
configured when it is for vendor, otherwise the default profile of vendor. |
builder(EDbVendor vendor) (static) |
IdentifierProfile.Builder |
A builder that starts from the default rules and VendorFlags.defaults(). |
builder(EDbVendor vendor, VendorFlags flags) (static) |
IdentifierProfile.Builder |
A builder that starts from the default rules for these flags. |
toBuilder() |
IdentifierProfile.Builder |
A builder that starts from this profile. |
getRules(ESQLDataObjectType objectType) |
IdentifierRules |
The rule of one object type. For null, the rule of NAME_GROUP. |
ruleDomainOf(ESQLDataObjectType objectType) |
IdentifierRules |
The rule of one object type with SAME_AS_UNQUOTED replaced by the unquoted compare. |
groupOf(ESQLDataObjectType objectType) |
IdentifierProfile.ObjectGroup |
The group of an object type (section 1.6). |
getVendor() |
EDbVendor |
The vendor. |
getFlags() |
VendorFlags |
The flags. |
getObjectRuleOverrides() |
Map<ESQLDataObjectType, IdentifierRules> |
The rules that you set with withObjectRules. |
hasSameEffectivePolicyAs(IdentifierProfile other) |
boolean |
Whether two profiles give the same answer to every name question. equals is the same test. |
getFingerprint() |
long |
A hash of the profile. Two different profiles can have the same fingerprint. |
IdentifierProfile.ObjectGroup: NAME_GROUP, COLUMN_GROUP, ROUTINE_GROUP.
IdentifierProfile.VendorFlags (section 8.1):
| Member | Description |
|---|---|
VendorFlags(int mysqlLowerCaseTableNames, String defaultCollation, boolean redshiftEnableCaseSensitive, boolean snowflakeQuotedIdentifiersIgnoreCase) |
Constructor. mysqlAnsiQuotes is false. |
VendorFlags(int mysqlLowerCaseTableNames, String defaultCollation, boolean redshiftEnableCaseSensitive, boolean snowflakeQuotedIdentifiersIgnoreCase, boolean mysqlAnsiQuotes) |
Constructor with all five settings. |
defaults() (static) |
1, "SQL_Latin1_General_CP1_CI_AS", false, false, false. |
mysqlLowerCaseTableNames, defaultCollation, redshiftEnableCaseSensitive, snowflakeQuotedIdentifiersIgnoreCase, mysqlAnsiQuotes |
Public final fields. |
IdentifierProfile.Builder (section 8.3):
| Method | Description |
|---|---|
withObjectRules(ESQLDataObjectType objectType, IdentifierRules rules) |
Sets the rule of one object type. The last call for a type wins. |
withoutObjectRules(ESQLDataObjectType objectType) |
Removes the rule of one object type. |
withRules(IdentifierProfile.ObjectGroup group, IdentifierRules rules) |
Sets the rule of one group. |
withNameRules(IdentifierRules rules), withColumnRules(IdentifierRules rules), withRoutineRules(IdentifierRules rules) |
Set the rule of NAME_GROUP, COLUMN_GROUP or ROUTINE_GROUP. |
withoutRules(IdentifierProfile.ObjectGroup group) |
Removes the rule of one group. |
withFlags(VendorFlags flags) |
Replaces the flags and computes the default rules again. Your own rules stay. |
build() |
Returns the profile. |
All builder methods except build() return the builder. They throw a
NullPointerException for a null argument.
13.4 IdentifierRules¶
| Member | Description |
|---|---|
IdentifierRules(CaseFold unquotedFold, CaseCompare unquotedCompare, CaseFold quotedFold, CaseCompare quotedCompare) |
Constructor. No argument can be null. |
unquotedFold, unquotedCompare, quotedFold, quotedCompare |
Public final fields. |
resolvedCell() |
The same rule with SAME_AS_UNQUOTED replaced by the unquoted compare. |
IdentifierRules.CaseFold |
NONE, UPPER, LOWER. |
IdentifierRules.CaseCompare |
SENSITIVE, INSENSITIVE, COLLATION_BASED, SAME_AS_UNQUOTED. |
Static factory methods return the rule that GSP uses for a database: forOracle(),
forDameng(), forDB2(), forHANA(), forFirebird(), forAnsi(), forGeneric(),
forSnowflake(), forSnowflake(boolean quotedIdentifiersIgnoreCase),
forPostgreSQL(), forGaussDB(), forRedshift(), forPresto(), forVertica(),
forAthena(), forHive(), forDatabricks(), forTeradata(), forDuckDB(),
forSqlite(), forSybase(), forSqlAnywhere(), forAccess(), forDax(), forMdx(),
forPowerQuery(), forFlink(), forCouchbase(), forSQLServer(),
forSQLServer(String collation), forMySQL(int lowerCaseTableNames),
forMySQLColumn(), forMySQLRoutine(), forDoris(), forDoris(int lowerCaseTableNames),
forDorisColumn(), forStarrocks(), forStarrocks(int lowerCaseTableNames),
forStarrocksColumn(), forBigQueryTable(), forBigQueryColumn(). Use them as a
starting point for your own rules.
13.5 IdentifierCodec¶
All methods are static. profile and objectType must not be null.
| Member | Returns | Description |
|---|---|---|
decodeLexical(IdentifierProfile profile, ESQLDataObjectType objectType, String lexical) |
String |
SQL text to stored name. Unquoted text comes back unchanged. Throws MalformedIdentifierException for broken delimited text. |
encodeStored(IdentifierProfile profile, ESQLDataObjectType objectType, String stored) |
String |
Stored name to SQL text, always delimited (SOQL: unchanged). Works for every string. |
isQuoted(IdentifierProfile profile, ESQLDataObjectType objectType, String s) |
boolean |
Whether s starts with a delimiter of this database and object type. false for null and "". It does not check that the rest of the text is correct. |
isVendorValidSpelling(IdentifierProfile profile, ESQLDataObjectType objectType, String lexical) |
boolean |
Whether the characters and the delimiters are valid. Reserved words and length limits are not checked. false for null and "". |
hasQuotedForm(IdentifierProfile profile, ESQLDataObjectType objectType) |
boolean |
Whether the database has any delimiter for this object type. |
requiresDelimiters(IdentifierProfile profile, ESQLDataObjectType objectType) |
boolean |
Whether a name must always be delimited (DAX columns). |
decodeClickHouseStringLiteral(String rawLiteral) |
String |
Decodes a ClickHouse string literal (not a name). null when the literal is broken. |
CODEC_REVISION |
int |
The revision of the delimiter table (1 in 4.2.10). |
IdentifierCodec.MalformedIdentifierException |
Extends IllegalArgumentException. |
13.6 CanonKey and QualifiedCanonKey¶
Both are in-memory keys with equals and hashCode. Neither has a public constructor:
get them from SQLUtil or IdentifierService. Do not save them.
CanonKey method |
Returns | Description |
|---|---|---|
getVendor() |
EDbVendor |
The vendor of the rule. |
getGroup() |
IdentifierProfile.ObjectGroup |
The object group. |
getCanonText() |
String |
The canonical text. For a collation key, it is for information only. |
isCollationBased() |
boolean |
Whether the key compares with a collation (SQL Server, Azure SQL). |
getRuleDomain() |
IdentifierRules |
The rule that made the key. |
getCollatorDomainId() |
String |
For a collation key, an ID of its collation and collator; otherwise null. |
sameRulePolicyAs(CanonKey other) |
boolean |
Whether both keys were made for the same vendor under profiles with the same rules. |
isRuleCompatibleWith(CanonKey other) |
boolean |
Whether two keys can be parts of one qualified key. |
describeRuleDomain() |
String |
A description for logs. |
QualifiedCanonKey method |
Returns | Description |
|---|---|---|
getSegmentKeys() |
List<CanonKey> |
One key for each segment, outermost first. |
getLeafKey() |
CanonKey |
The key of the last segment, for example the column of t.c. |
getTerminalType() |
ESQLDataObjectType |
The kind of the whole name. |
size() |
int |
The number of segments. |
getVendor() |
EDbVendor |
The vendor. |
13.7 QualifiedNameHierarchy¶
| Method | Returns | Description |
|---|---|---|
hasHierarchyMapping(EDbVendor vendor, ESQLDataObjectType objectType) (static) |
boolean |
Whether qualified names of this object type can be compared and keyed (section 4.2). Call it before qualifiedCanonKeyFromLexical when your code can get any object type. |
13.8 Persistent keys¶
| Type | Members |
|---|---|
PersistentIdentifierKey |
PersistentIdentifierKey(String policyId, IdentifierProfile.ObjectGroup group, String payload) rebuilds a saved key. getPolicyId() (starts with pk1-), getGroup(), getPayload(). equals and hashCode use all three parts. |
ApproximateIdentifierKey |
The same constructor and getters. The policy ID starts with ak1-. It is never equal to a PersistentIdentifierKey. |
IdentifierInputForm |
SQL_LEXICAL: SQL text. CATALOG_STORED: a name read from the catalog of the database. |
13.9 SqlNameGenerator¶
| Method (all static) | Returns | Description |
|---|---|---|
generate(EDbVendor vendor, Segment... segments) |
Result |
SQL text from stored segments, every segment delimited (ALWAYS). |
generate(EDbVendor vendor, List<Segment> segments) |
Result |
The same with a list. |
generate(EDbVendor vendor, QuotePolicy policy, Segment... segments) |
Result |
SQL text with a quote policy. |
generate(EDbVendor vendor, QuotePolicy policy, List<Segment> segments) |
Result |
The same with a list. |
respell(EDbVendor vendor, QuotePolicy policy, ESQLDataObjectType role, String lexical) |
Result |
Rewrites one name read from SQL text (section 7). |
| Nested type | Members |
|---|---|
SqlNameGenerator.QuotePolicy |
ALWAYS, WHEN_NEEDED |
SqlNameGenerator.Segment |
of(ESQLDataObjectType role, String stored), server(String), catalog(String), schema(String), table(String), column(String), function(String), procedure(String), getRole(), getStored() |
SqlNameGenerator.Result |
isOk(), getSql() (null on failure), getFailure() (null on success), getFailedSegmentIndex() (-1 when no segment is at fault), getDetail(), getSqlOrThrow() |
SqlNameGenerator.Failure |
NO_SEGMENTS, NULL_SEGMENT, EMPTY_SEGMENT, NO_QUOTED_FORM, NOT_REPRESENTABLE, ROUND_TRIP_MISMATCH |
13.10 NameService¶
| Method | Returns | Description |
|---|---|---|
NameService(EDbVendor vendor) |
Constructor with the default rules of the vendor. | |
forIdentifierService(IdentifierService service) (static) |
NameService |
A name service with the rules of any service. |
equals(ESQLDataObjectType type, String name1, String name2) |
boolean |
The same as areEqual of the service. |
indexOf(ESQLDataObjectType type, Iterable<String> names, String target) |
int |
The position of the first matching name, or -1. |
distinctCopy(ESQLDataObjectType type, Iterable<String> names) |
List<String> |
The names without duplicates, first spelling kept. null elements are skipped. |
createSet(ESQLDataObjectType type) |
Set<String> |
A set that uses the name rules. It does not accept null. |
isCaseSensitive(ESQLDataObjectType type) |
boolean |
Whether unquoted names of this type are compared case-sensitively. |
getIdentifierService(), getIdentifierProfile(), getVendor() |
The service, the profile and the vendor. |
13.11 ESQLDataObjectType¶
dotUnknown, dotCatalog, dotSchema, dotTable, dotColumn, dotRoutine,
dotOraclePackage, dotProcedure, dotFunction, dotTrigger, dotSynonyms,
dotDblink, dotDataType, dotParameter, dotServer, dotSequence, dotIndex,
dotConstraint. The groups are in section 1.6. There is no separate
type for views: use dotTable.
13.12 Analysis entry points¶
| Class | Method | Description |
|---|---|---|
TGSqlParser (gudusoft.gsqlparser) |
setIdentifierProfile(IdentifierProfile profile) |
Sets the profile of the next parse. Call it before parse(). null means the default rules. |
TGSqlParser |
getIdentifierProfile() |
The profile of the parse. null before the first parse when no profile and no TSQLEnv were given. |
TGSqlParser |
getSqlEnv() |
After parse(), the environment of the parse. Its getIdentifierService() is the service that the parse used. |
TGSqlParser |
setSqlEnv(TSQLEnv sqlEnv), setResolver2Config(TSQLResolverConfig config), prepareForReuse() |
See section 9. |
TSQLEnv |
TSQLEnv(EDbVendor dbVendor), TSQLEnv(EDbVendor dbVendor, IdentifierProfile identifierProfile) |
Constructors of this abstract class (implement initSQLEnv()). The profile must be for dbVendor. |
TSQLEnv |
getIdentifierService(), getIdentifierProfile() |
The service and the profile of the environment. |
TSQLEnv |
toLexical(ESQLDataObjectType objectType, String name, IdentifierInputForm form) |
Converts a CATALOG_STORED name into SQL text with the rules of the environment. A SQL_LEXICAL name comes back unchanged. |
TSQLEnv |
toLexical(EDbVendor vendor, ESQLDataObjectType objectType, String name, IdentifierInputForm form) (static) |
The same with the default rules of the vendor. |
TSQLEnv |
compareIdentifier(EDbVendor dbVendor, ESQLDataObjectType objectType, String ident1, String ident2) (static) |
The same as SQLUtil.sameName. |
TSQLResolverConfig (gudusoft.gsqlparser.resolver2) |
createForProfile(IdentifierProfile profile) (static), setIdentifierProfile(IdentifierProfile profile), getIdentifierProfile(), identifierService() |
The profile of resolver2 (section 9.3). |
Option (gudusoft.gsqlparser.dlineage.dataflow.model) |
setIdentifierProfile(IdentifierProfile identifierProfile), getIdentifierProfile() |
The profile of a lineage run (section 9.4). null means the default rules. |
13.13 Display modes¶
| Type or method | Description |
|---|---|
DisplayNameMode (gudusoft.gsqlparser.resolver2.format) |
DISPLAY, SQL_RENDER, CANONICAL (section 6.3). |
DisplayNamePolicy (gudusoft.gsqlparser.resolver2.format) |
PREFER_DEFINITION_SITE, PREFER_FIRST_OCCURRENCE, PREFER_METADATA. |
TGetTableColumn.setDisplayNameMode(DisplayNameMode), setQuotePolicy(SqlNameGenerator.QuotePolicy), setDisplayNamePolicy(DisplayNamePolicy) |
Settings of the TGetTableColumn report (gudusoft.gsqlparser.util). |
TSQLResolver2ResultFormatter.setDisplayNameMode(DisplayNameMode), setQuotePolicy(SqlNameGenerator.QuotePolicy) |
Settings of the resolver2 report. |
TSQLResolverConfig.setDisplayNameMode(DisplayNameMode), setDisplayNamePolicy(DisplayNamePolicy) |
Defaults for the resolver2 report. |
Refreshing this page¶
This page is written by hand, but every output on it is measured. To bring it forward to a newer GSP release:
1. Read the version from TBaseType.versionid and TBaseType.releaseDate in
gsp_java_core/src/main/java/gudusoft/gsqlparser/TBaseType.java.
2. Run every sample again. Each Java block on this page is a complete program. Compile it against the new jar, run it, and paste the new output. Do not edit an output block by hand. The policy IDs in section 10 change when GSP changes the rules of that vendor or object group.
3. Measure the two tables again. The delimiter table in section 1.2 and
the rule table in section 1.5 come from the program below. It prints one
line for each EDbVendor value. Compare its output with the two tables, and look for new
vendors.
The program that measures the tables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
4. Check the statements that name a version. Sections 8.1, 8.2, 9.2 and 9.5 describe the behavior of 4.2.10. When a newer release changes it, update the text.
5. Update the stamp at the top: the version, its release date, today's date, and the number of samples. The stamp says which build the samples were run on. Change it only after you have run the samples on the new build.
6. Verify before you publish:
1 2 3 | |
See also¶
- Custom identifier rules: a short how-to guide
for
IdentifierProfileand its builder. - Data lineage options, explained: every
Optionsetting ofDataFlowAnalyzer. - API documentation: the generated Javadoc.