From 62dffe1d1678ba20fe53d78caf0df8c5fbd2e106 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 24 Oct 2026 17:08:27 +0200 Subject: [PATCH] refactor(jdbc): extract AUTOINCREMENT grammar --- .../libsql/jdbc/LibsqlDatabaseMetaData.java | 2 +- .../libsql/jdbc/LibsqlResultSetMetaData.java | 142 +---------------- .../magnusp/libsql/jdbc/SqlAutoIncrement.java | 148 ++++++++++++++++++ 3 files changed, 152 insertions(+), 140 deletions(-) create mode 100644 libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlAutoIncrement.java diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java index 1616c46..2cd2878 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java @@ -548,7 +548,7 @@ private static StmtResult columnsResult(StmtResult raw) { ColumnShape shape = columnShape(declaredType); boolean autoIncrement = primaryKey - && LibsqlResultSetMetaData.isAutoIncrementColumn(createSql, columnName); + && SqlAutoIncrement.isAutoIncrementColumn(createSql, columnName); boolean generated = hidden == 2 || hidden == 3; rows.add(new Row(List.of( 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 ed882b0..2138ea9 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 @@ -67,24 +67,9 @@ private Col getCol(int column) throws SQLException { public boolean isAutoIncrement(int column) throws SQLException { TableInfo table = tableForColumn(column); ColumnInfo info = table != null ? table.columns().get(columnName(column)) : null; - return info != null && info.primaryKey() && isAutoIncrementColumn(table.createSql(), columnName(column)); - } - - static boolean isAutoIncrementColumn(String createSql, String columnName) { - if (createSql == null || columnName == null) { - return false; - } - List> definitions = tableDefinitions(createSql); - if (definitions == null) { - return false; - } - for (List definition : definitions) { - if (columnDefinitionIsAutoIncrement(definition, columnName) - || tableConstraintIsAutoIncrement(definition, columnName)) { - return true; - } - } - return false; + return info != null + && info.primaryKey() + && SqlAutoIncrement.isAutoIncrementColumn(table.createSql(), columnName(column)); } @Override @@ -526,127 +511,6 @@ private static TableInfo toTableInfo(StmtResult result, String fallbackName) { return new TableInfo(tableName != null ? tableName : fallbackName, createSql, columns); } - private static List> tableDefinitions(String createSql) { - List tokens = SqlTokenizer.tokenize(createSql); - if (tokens == null) { - return null; - } - int open = -1; - for (int i = 0; i < tokens.size(); i++) { - if (tokens.get(i).isSymbol("(")) { - open = i; - break; - } - } - if (open < 0) { - return null; - } - - List> definitions = new ArrayList<>(); - List current = new ArrayList<>(); - for (int i = open + 1; i < tokens.size(); i++) { - SqlTokenizer.Token token = tokens.get(i); - if (token.isSymbol(")") && token.depth() == 0) { - if (!current.isEmpty()) { - definitions.add(current); - } - return definitions; - } - if (token.isSymbol(",") && token.depth() == 1) { - definitions.add(current); - current = new ArrayList<>(); - continue; - } - current.add(token); - } - return null; - } - - private static boolean columnDefinitionIsAutoIncrement( - List definition, String columnName) { - int start = skipConstraintName(definition); - if (start >= definition.size()) { - return false; - } - if (isConstraintKeyword(definition.get(start))) { - return false; - } - String name = definition.get(start).identifier(); - if (name == null || !name.equalsIgnoreCase(columnName)) { - return false; - } - return containsKeyword(definition, start + 1, definition.size(), "AUTOINCREMENT", 1); - } - - private static boolean tableConstraintIsAutoIncrement( - List definition, String columnName) { - int start = skipConstraintName(definition); - if (start + 1 >= definition.size() - || !definition.get(start).isWord("PRIMARY") - || !definition.get(start + 1).isWord("KEY")) { - return false; - } - int open = -1; - for (int i = start + 2; i < definition.size(); i++) { - if (definition.get(i).isSymbol("(")) { - open = i; - break; - } - } - if (open < 0) { - return false; - } - - int partStart = open + 1; - for (int i = open + 1; i < definition.size(); i++) { - SqlTokenizer.Token token = definition.get(i); - if ((token.isSymbol(",") && token.depth() == 2) - || (token.isSymbol(")") && token.depth() == 1)) { - if (indexedColumnIsAutoIncrement(definition, partStart, i, columnName)) { - return true; - } - partStart = i + 1; - } - } - return false; - } - - private static boolean indexedColumnIsAutoIncrement( - List definition, int start, int end, String columnName) { - if (start >= end) { - return false; - } - String name = definition.get(start).identifier(); - return name != null - && name.equalsIgnoreCase(columnName) - && containsKeyword(definition, start + 1, end, "AUTOINCREMENT", 2); - } - - private static int skipConstraintName(List definition) { - if (definition.size() >= 2 && definition.get(0).isWord("CONSTRAINT")) { - return 2; - } - return 0; - } - - private static boolean isConstraintKeyword(SqlTokenizer.Token token) { - return token.isWord("PRIMARY") - || token.isWord("UNIQUE") - || token.isWord("CHECK") - || token.isWord("FOREIGN"); - } - - private static boolean containsKeyword( - List tokens, int start, int end, String keyword, int depth) { - for (int i = start; i < end; i++) { - SqlTokenizer.Token token = tokens.get(i); - if (token.depth() == depth && token.isWord(keyword)) { - return true; - } - } - return false; - } - private static String text(Value value) { return value instanceof Value.Text t ? t.value() : null; } diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlAutoIncrement.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlAutoIncrement.java new file mode 100644 index 0000000..a25c381 --- /dev/null +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/SqlAutoIncrement.java @@ -0,0 +1,148 @@ +package com.github.magnusp.libsql.jdbc; + +import java.util.ArrayList; +import java.util.List; + +/** SQLite AUTOINCREMENT grammar for a column definition. */ +final class SqlAutoIncrement { + + private SqlAutoIncrement() {} + + static boolean isAutoIncrementColumn(String createSql, String columnName) { + if (createSql == null || columnName == null) { + return false; + } + List> definitions = tableDefinitions(createSql); + if (definitions == null) { + return false; + } + for (List definition : definitions) { + if (columnDefinitionIsAutoIncrement(definition, columnName) + || tableConstraintIsAutoIncrement(definition, columnName)) { + return true; + } + } + return false; + } + + private static List> tableDefinitions(String createSql) { + List tokens = SqlTokenizer.tokenize(createSql); + if (tokens == null) { + return null; + } + int open = -1; + for (int i = 0; i < tokens.size(); i++) { + if (tokens.get(i).isSymbol("(")) { + open = i; + break; + } + } + if (open < 0) { + return null; + } + + List> definitions = new ArrayList<>(); + List current = new ArrayList<>(); + for (int i = open + 1; i < tokens.size(); i++) { + SqlTokenizer.Token token = tokens.get(i); + if (token.isSymbol(")") && token.depth() == 0) { + if (!current.isEmpty()) { + definitions.add(current); + } + return definitions; + } + if (token.isSymbol(",") && token.depth() == 1) { + definitions.add(current); + current = new ArrayList<>(); + continue; + } + current.add(token); + } + return null; + } + + private static boolean columnDefinitionIsAutoIncrement( + List definition, String columnName) { + int start = skipConstraintName(definition); + if (start >= definition.size()) { + return false; + } + if (isConstraintKeyword(definition.get(start))) { + return false; + } + String name = definition.get(start).identifier(); + if (name == null || !name.equalsIgnoreCase(columnName)) { + return false; + } + return containsKeyword(definition, start + 1, definition.size(), "AUTOINCREMENT", 1); + } + + private static boolean tableConstraintIsAutoIncrement( + List definition, String columnName) { + int start = skipConstraintName(definition); + if (start + 1 >= definition.size() + || !definition.get(start).isWord("PRIMARY") + || !definition.get(start + 1).isWord("KEY")) { + return false; + } + int open = -1; + for (int i = start + 2; i < definition.size(); i++) { + if (definition.get(i).isSymbol("(")) { + open = i; + break; + } + } + if (open < 0) { + return false; + } + + int partStart = open + 1; + for (int i = open + 1; i < definition.size(); i++) { + SqlTokenizer.Token token = definition.get(i); + if ((token.isSymbol(",") && token.depth() == 2) + || (token.isSymbol(")") && token.depth() == 1)) { + if (indexedColumnIsAutoIncrement(definition, partStart, i, columnName)) { + return true; + } + partStart = i + 1; + } + } + return false; + } + + private static boolean indexedColumnIsAutoIncrement( + List definition, int start, int end, String columnName) { + if (start >= end) { + return false; + } + String name = definition.get(start).identifier(); + return name != null + && name.equalsIgnoreCase(columnName) + && containsKeyword(definition, start + 1, end, "AUTOINCREMENT", 2); + } + + private static int skipConstraintName(List definition) { + if (definition.size() >= 2 && definition.get(0).isWord("CONSTRAINT")) { + return 2; + } + return 0; + } + + private static boolean isConstraintKeyword(SqlTokenizer.Token token) { + return token.isWord("PRIMARY") + || token.isWord("UNIQUE") + || token.isWord("CHECK") + || token.isWord("FOREIGN"); + } + + private static boolean containsKeyword( + List tokens, int start, int end, String keyword, int depth) { + for (int i = start; i < end; i++) { + SqlTokenizer.Token token = tokens.get(i); + if (token.depth() == depth && token.isWord(keyword)) { + return true; + } + } + return false; + } +}