diff --git a/driver.go b/driver.go index b0e416b..3d57077 100644 --- a/driver.go +++ b/driver.go @@ -11,28 +11,30 @@ var ( drivers = make(map[string]Driver) ) -// Driver interface defines the contract for database-specific implementations. -// Each database driver handles SQL dialect differences, keyword quoting, and placeholders. -// Functions are handled generically by the SQL builder without driver-specific translation. -type Driver interface { - // Name returns the driver name (e.g., "postgres", "mysql", "clickhouse"). - Name() string +type ( + // Driver interface defines the contract for database-specific implementations. + // Each database driver handles SQL dialect differences, keyword quoting, and placeholders. + // Functions are handled generically by the SQL builder without driver-specific translation. + Driver interface { + // Name returns the driver name (e.g., "postgres", "mysql", "clickhouse"). + Name() string - // QuoteIdentifier quotes database identifiers to handle reserved keywords and special characters. - QuoteIdentifier(name string) string + // QuoteIdentifier quotes database identifiers to handle reserved keywords and special characters. + QuoteIdentifier(name string) string - // Placeholder returns the placeholder syntax for the given parameter position. - Placeholder(position int) string + // Placeholder returns the placeholder syntax for the given parameter position. + Placeholder(position int) string - // IsReservedKeyword returns true if the word is a reserved keyword in this database. - IsReservedKeyword(word string) bool + // IsReservedKeyword returns true if the word is a reserved keyword in this database. + IsReservedKeyword(word string) bool - // TranslateOperator translates an operator to database-specific syntax. - TranslateOperator(op string) (translated string, supported bool) + // TranslateOperator translates an operator to database-specific syntax. + TranslateOperator(op string) (translated string, supported bool) - // SupportsFeature returns true if the database supports the named feature. - SupportsFeature(feature string) bool -} + // SupportsFeature returns true if the database supports the named feature. + SupportsFeature(feature string) bool + } +) // RegisterDriver registers a database driver with the given name. // This function is typically called from driver package init() functions. @@ -74,3 +76,30 @@ func ListDrivers() []string { } return names } + +// NeedsQuoting determines if an identifier needs to be quoted. +// This implements the common SQL identifier quoting rules used across all database drivers. +func NeedsQuoting(name string, driver Driver) bool { + if driver.IsReservedKeyword(name) { + return true + } + + if name == "" { + return false + } + + if name[0] >= '0' && name[0] <= '9' { + return true + } + + for _, ch := range name { + if (ch < 'a' || ch > 'z') && + (ch < 'A' || ch > 'Z') && + (ch < '0' || ch > '9') && + ch != '_' { + return true + } + } + + return false +} diff --git a/drivers/clickhouse/clickhouse.go b/drivers/clickhouse/clickhouse.go index fd05837..626e322 100644 --- a/drivers/clickhouse/clickhouse.go +++ b/drivers/clickhouse/clickhouse.go @@ -2,16 +2,31 @@ package clickhouse import ( "fmt" + "slices" "strings" "github.com/pseudomuto/where" ) -// ClickHouseDriver implements the where.Driver interface for ClickHouse databases. -type ClickHouseDriver struct { - keywords map[string]bool +var supportsFeatures = []string{ + "ARRAY", + "FINAL", + "GLOBAL", + "ILIKE", + "JSON", + "PREWHERE", + "SAMPLE", + "TUPLE", + "WITH", } +type ( + // ClickHouseDriver implements the where.Driver interface for ClickHouse databases. + ClickHouseDriver struct { + keywords map[string]bool + } +) + // NewClickHouseDriver creates a new ClickHouse driver instance. // // Example: @@ -60,37 +75,12 @@ func (d *ClickHouseDriver) QuoteIdentifier(name string) string { } func (d *ClickHouseDriver) quoteSimpleIdentifier(name string) string { - if d.needsQuoting(name) { + if where.NeedsQuoting(name, d) { return fmt.Sprintf("`%s`", strings.ReplaceAll(name, "`", "``")) } return name } -func (d *ClickHouseDriver) needsQuoting(name string) bool { - if d.IsReservedKeyword(name) { - return true - } - - if name == "" { - return false - } - - if name[0] >= '0' && name[0] <= '9' { - return true - } - - for _, ch := range name { - if (ch < 'a' || ch > 'z') && - (ch < 'A' || ch > 'Z') && - (ch < '0' || ch > '9') && - ch != '_' { - return true - } - } - - return false -} - func (d *ClickHouseDriver) Placeholder(position int) string { return "?" } @@ -118,14 +108,7 @@ func (d *ClickHouseDriver) TranslateOperator(op string) (string, bool) { } func (d *ClickHouseDriver) SupportsFeature(feature string) bool { - switch strings.ToUpper(feature) { - case "ILIKE", "ARRAY", "TUPLE", "WITH", "SAMPLE", "PREWHERE", "FINAL", "GLOBAL": - return true - case "JSON": - return true - default: - return false - } + return slices.Contains(supportsFeatures, strings.ToUpper(feature)) } func init() { diff --git a/drivers/mysql/mysql.go b/drivers/mysql/mysql.go index 33005d7..33903c3 100644 --- a/drivers/mysql/mysql.go +++ b/drivers/mysql/mysql.go @@ -2,16 +2,27 @@ package mysql import ( "fmt" + "slices" "strings" "github.com/pseudomuto/where" ) -// MySQLDriver implements the where.Driver interface for MySQL and MariaDB databases. -type MySQLDriver struct { - keywords map[string]bool +var supportedFeatures = []string{ + "CTE", + "FULLTEXT", + "JSON", + "PARTITION", + "SPATIAL", } +type ( + // MySQLDriver implements the where.Driver interface for MySQL and MariaDB databases. + MySQLDriver struct { + keywords map[string]bool + } +) + // NewMySQLDriver creates a new MySQL driver instance. // // Example: @@ -60,37 +71,12 @@ func (d *MySQLDriver) QuoteIdentifier(name string) string { } func (d *MySQLDriver) quoteSimpleIdentifier(name string) string { - if d.needsQuoting(name) { + if where.NeedsQuoting(name, d) { return fmt.Sprintf("`%s`", strings.ReplaceAll(name, "`", "``")) } return name } -func (d *MySQLDriver) needsQuoting(name string) bool { - if d.IsReservedKeyword(name) { - return true - } - - if name == "" { - return false - } - - if name[0] >= '0' && name[0] <= '9' { - return true - } - - for _, ch := range name { - if (ch < 'a' || ch > 'z') && - (ch < 'A' || ch > 'Z') && - (ch < '0' || ch > '9') && - ch != '_' { - return true - } - } - - return false -} - func (d *MySQLDriver) Placeholder(position int) string { return "?" } @@ -122,14 +108,7 @@ func (d *MySQLDriver) TranslateOperator(op string) (string, bool) { } func (d *MySQLDriver) SupportsFeature(feature string) bool { - switch strings.ToUpper(feature) { - case "JSON", "FULLTEXT", "SPATIAL", "PARTITION", "CTE": - return true - case "ILIKE": - return false - default: - return false - } + return slices.Contains(supportedFeatures, strings.ToUpper(feature)) } func init() { diff --git a/drivers/postgres/postgres.go b/drivers/postgres/postgres.go index 67c3391..89b0311 100644 --- a/drivers/postgres/postgres.go +++ b/drivers/postgres/postgres.go @@ -2,16 +2,29 @@ package postgres import ( "fmt" + "slices" "strings" "github.com/pseudomuto/where" ) -// PostgreSQLDriver implements the where.Driver interface for PostgreSQL databases. -type PostgreSQLDriver struct { - keywords map[string]bool +var supportedFeatures = []string{ + "ARRAY", + "CTE", + "ILIKE", + "JSON", + "JSONB", + "RETURNING", + "WINDOW", } +type ( + // PostgreSQLDriver implements the where.Driver interface for PostgreSQL databases. + PostgreSQLDriver struct { + keywords map[string]bool + } +) + // NewPostgreSQLDriver creates a new PostgreSQL driver instance. // // Example: @@ -60,37 +73,12 @@ func (d *PostgreSQLDriver) QuoteIdentifier(name string) string { } func (d *PostgreSQLDriver) quoteSimpleIdentifier(name string) string { - if d.needsQuoting(name) { + if where.NeedsQuoting(name, d) { return fmt.Sprintf(`"%s"`, strings.ReplaceAll(name, `"`, `""`)) } return name } -func (d *PostgreSQLDriver) needsQuoting(name string) bool { - if d.IsReservedKeyword(name) { - return true - } - - if name == "" { - return false - } - - if name[0] >= '0' && name[0] <= '9' { - return true - } - - for _, ch := range name { - if (ch < 'a' || ch > 'z') && - (ch < 'A' || ch > 'Z') && - (ch < '0' || ch > '9') && - ch != '_' { - return true - } - } - - return false -} - func (d *PostgreSQLDriver) Placeholder(position int) string { return fmt.Sprintf("$%d", position) } @@ -118,12 +106,7 @@ func (d *PostgreSQLDriver) TranslateOperator(op string) (string, bool) { } func (d *PostgreSQLDriver) SupportsFeature(feature string) bool { - switch strings.ToUpper(feature) { - case "ILIKE", "ARRAY", "JSON", "JSONB", "RETURNING", "CTE", "WINDOW": - return true - default: - return false - } + return slices.Contains(supportedFeatures, strings.ToUpper(feature)) } func init() {