Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 46 additions & 17 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
57 changes: 20 additions & 37 deletions drivers/clickhouse/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 "?"
}
Expand Down Expand Up @@ -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() {
Expand Down
53 changes: 16 additions & 37 deletions drivers/mysql/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 "?"
}
Expand Down Expand Up @@ -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() {
Expand Down
53 changes: 18 additions & 35 deletions drivers/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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() {
Expand Down
Loading