diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java index 9d139c6..f0d3114 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaData.java @@ -762,7 +762,12 @@ private static String unescape(String content, char quote) { } private static boolean isWordChar(char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$'; + return c >= 0x80 + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_' + || c == '$'; } private static String text(Value value) { diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlTableReference.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlTableReference.java index 6beaf1f..41229f5 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlTableReference.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlTableReference.java @@ -121,7 +121,8 @@ static FromClause of(List sources) { } } - private static final Pattern UNQUOTED_IDENTIFIER = Pattern.compile("[A-Za-z_][A-Za-z0-9_$]*"); + private static final Pattern UNQUOTED_IDENTIFIER = + Pattern.compile("[A-Za-z_\\x80-\\uFFFF][A-Za-z0-9_$\\x80-\\uFFFF]*"); private static final Set FROM_END = Set.of( "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "WINDOW", "RETURNING", @@ -1273,6 +1274,11 @@ private static String unescape(String content, char quote) { } private static boolean isWordChar(char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$'; + return c >= 0x80 + || (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '_' + || c == '$'; } } diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaDataTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaDataTest.java index 6a1002f..2414888 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaDataTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlResultSetMetaDataTest.java @@ -54,6 +54,30 @@ void resolvesTableNameNullabilityAndAutoIncrement() throws SQLException { assertThat(metadata.isAutoIncrement(2)).isFalse(); } + @Test + void resolvesNonAsciiTableName() throws SQLException { + LibsqlResultSetMetaData metadata = new LibsqlResultSetMetaData( + List.of(new Col("namn_ö", "TEXT")), + List.of(), + "SELECT namn_ö FROM användare", + connectionForSwedishTables()); + + assertThat(metadata.getTableName(1)).isEqualTo("användare"); + assertThat(metadata.isNullable(1)).isEqualTo(ResultSetMetaData.columnNullable); + } + + @Test + void prefersNonAsciiTableOverItsAsciiPrefix() throws SQLException { + LibsqlResultSetMetaData metadata = new LibsqlResultSetMetaData( + List.of(new Col("namn_ö", "TEXT")), + List.of(), + "SELECT namn_ö FROM användare", + connectionForSwedishTables()); + + assertThat(metadata.getTableName(1)).isEqualTo("användare"); + assertThat(metadata.getColumnName(1)).isEqualTo("namn_ö"); + } + @Test void ignoresAutoIncrementInsideSqlComments() throws SQLException { String createSql = "CREATE TABLE People (id INTEGER PRIMARY KEY /* AUTOINCREMENT */, name TEXT)"; @@ -644,6 +668,33 @@ private static LibsqlResultSetMetaData metadataForTable(String createSql, List tables = Map.of( + "anv", "CREATE TABLE anv (id INTEGER)", + "användare", "CREATE TABLE användare (id INTEGER, namn_ö TEXT)"); + Map> columns = Map.of("anv", List.of("id"), "användare", List.of("id", "namn_ö")); + return new LibsqlConnection(null, null) { + @Override + public StmtResult executeStatement(Stmt stmt) throws SQLException { + String table = argument(stmt, 0); + String createSql = tables.get(table); + if (createSql == null) { + return new StmtResult(List.of(), List.of(), 0, null); + } + List rows = new ArrayList<>(); + for (String column : columns.get(table)) { + rows.add(new Row(List.of( + Value.of(table), + Value.of(createSql), + Value.of(column), + Value.of(0L), + Value.of(0L)))); + } + return new StmtResult(List.of(), rows, 0, null); + } + }; + } + private static LibsqlConnection connectionReturning(StmtResult result) { return new LibsqlConnection(null, null) { @Override diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/SqlTableReferenceTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/SqlTableReferenceTest.java index 7608fb5..723a820 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/SqlTableReferenceTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/SqlTableReferenceTest.java @@ -29,7 +29,12 @@ class SqlTableReferenceTest { new SqlTableReference.TableDefinition("CREATE TABLE c (z TEXT)", List.of("z")), "g", new SqlTableReference.TableDefinition( - "CREATE TABLE g (p INTEGER, q AS (p+1), r TEXT NOT NULL)", List.of("p", "q", "r"))); + "CREATE TABLE g (p INTEGER, q AS (p+1), r TEXT NOT NULL)", List.of("p", "q", "r")), + "anv", + new SqlTableReference.TableDefinition("CREATE TABLE anv (id INTEGER)", List.of("id")), + "användare", + new SqlTableReference.TableDefinition( + "CREATE TABLE användare (id INTEGER, namn_ö TEXT)", List.of("id", "namn_ö"))); private static final SqlTableReference.Schema SCHEMA = reference -> { if (reference.table() == null) { @@ -58,6 +63,57 @@ void resolvesQuotedAndBracketedNames() throws SQLException { .containsExactly(new SqlTableReference.TableRef(null, "my table")); } + @Test + void resolvesNonAsciiTableAndColumn() throws SQLException { + SqlTableReference.Resolution resolution = resolve("SELECT namn_ö FROM användare"); + + assertThat(resolution.labels()).containsExactly("namn_ö"); + assertThat(resolution.tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + } + + @Test + void resolvesNonAsciiColumnThroughTableAlias() throws SQLException { + assertThat(resolve("SELECT a.namn_ö FROM användare a").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + assertThat(resolve("SELECT * FROM användare a").tables()) + .containsExactly( + new SqlTableReference.TableRef(null, "användare"), + new SqlTableReference.TableRef(null, "användare")); + } + + @Test + void resolvesNonAsciiAliases() throws SQLException { + assertThat(resolve("SELECT namn_ö AS namn FROM användare").labels()).containsExactly("namn"); + assertThat(resolve("SELECT namn_ö namn FROM användare").labels()).containsExactly("namn"); + assertThat(resolve("SELECT namn_ö AS namn FROM användare").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + assertThat(resolve("SELECT å.namn_ö FROM användare AS å").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + } + + @Test + void resolvesQuotedNonAsciiNames() throws SQLException { + assertThat(resolve("SELECT namn_ö FROM \"användare\"").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + assertThat(resolve("SELECT namn_ö FROM [användare]").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "användare")); + assertThat(resolve("SELECT \"namn_ö\" FROM \"användare\"").labels()).containsExactly("namn_ö"); + } + + @Test + void keepsNonAsciiTableWholeWhenAnAsciiPrefixTableExists() throws SQLException { + SqlTableReference.Resolution resolution = resolve("SELECT * FROM användare"); + + assertThat(resolution.labels()).containsExactly("id", "namn_ö"); + assertThat(resolution.tables()) + .containsExactly( + new SqlTableReference.TableRef(null, "användare"), + new SqlTableReference.TableRef(null, "användare")); + assertThat(resolve("SELECT id FROM anv").tables()) + .containsExactly(new SqlTableReference.TableRef(null, "anv")); + } + @Test void resolvesAliasesWithAndWithoutAs() throws SQLException { assertThat(resolve("SELECT a AS z FROM w").labels()).containsExactly("z");