mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-26 00:11:43 +08:00
# Conflicts: # frontend/src/App.tsx # frontend/src/components/AISettingsModal.tsx # frontend/src/components/ConnectionModal.edit-password.test.tsx # frontend/src/components/ConnectionModal.tsx # frontend/src/components/DataSyncModal.i18n.test.ts # frontend/src/components/DataSyncModal.tsx # frontend/src/components/QueryEditor.external-sql-save.test.tsx # frontend/src/components/QueryEditor.tsx # frontend/src/components/Sidebar.locate-toolbar.test.tsx # frontend/src/components/Sidebar.tsx # frontend/src/components/SnippetSettingsModal.tsx # frontend/src/components/TableOverview.tsx # frontend/src/components/ai/AIChatHeader.test.tsx # frontend/src/components/ai/AISettingsProvidersSection.tsx # frontend/src/components/ai/aiChatPayloadDispatch.ts # frontend/src/components/ai/aiChatReadiness.ts # frontend/src/components/ai/aiSettingsModalConfig.tsx # frontend/src/components/ai/messageBubble/AIMessageCodeBlock.tsx # frontend/src/components/sidebarV2Utils.ts # frontend/src/i18n/catalog.test.ts # frontend/src/utils/connectionTypeCatalog.test.ts # frontend/src/utils/connectionTypeCatalog.ts # frontend/src/utils/tabDisplay.ts # internal/ai/provider/custom.go # internal/ai/service/service.go # internal/app/methods_driver.go # internal/app/methods_file.go # internal/db/custom_impl.go # internal/db/iris_impl.go # internal/db/mariadb_impl.go # internal/db/sqlserver_impl.go # shared/i18n/de-DE.json # shared/i18n/en-US.json # shared/i18n/ja-JP.json # shared/i18n/ru-RU.json # shared/i18n/zh-CN.json # shared/i18n/zh-TW.json
559 lines
16 KiB
Go
559 lines
16 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
"GoNavi-Wails/internal/utils"
|
|
)
|
|
|
|
type CustomDB struct {
|
|
conn *sql.DB
|
|
driver string
|
|
pingTimeout time.Duration
|
|
}
|
|
|
|
func (c *CustomDB) Connect(config connection.ConnectionConfig) error {
|
|
driver := strings.TrimSpace(config.Driver)
|
|
dsn := strings.TrimSpace(config.DSN)
|
|
if driver == "" || dsn == "" {
|
|
return fmt.Errorf("driver and dsn are required for custom connection")
|
|
}
|
|
if strings.EqualFold(driver, "mysql") {
|
|
dsn = normalizeMySQLRawDSNCompatibilityParams(dsn)
|
|
}
|
|
|
|
// Verify driver is registered (implicit check by sql.Open)
|
|
// We might not need explicit check, sql.Open will fail or Ping will fail if driver not found.
|
|
|
|
db, err := sql.Open(driver, dsn)
|
|
if err != nil {
|
|
return formatCustomDriverOpenError(driver, err)
|
|
}
|
|
configureSQLConnectionPool(db, driver)
|
|
c.conn = db
|
|
c.driver = driver
|
|
c.pingTimeout = getConnectTimeout(config)
|
|
if err := c.Ping(); err != nil {
|
|
_ = db.Close()
|
|
c.conn = nil
|
|
return wrapDatabaseConnectionVerifyError(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func formatCustomDriverOpenError(driver string, err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if strings.Contains(strings.ToLower(err.Error()), "unknown driver") {
|
|
if isLikelySystemODBCDriverName(driver) {
|
|
return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.custom_driver_system_odbc_unsupported_prefix", map[string]any{
|
|
"driver": driver,
|
|
}), err)
|
|
}
|
|
return fmt.Errorf("%s%w", localizedDriverRuntimeText("db.backend.error.custom_driver_unregistered_prefix", map[string]any{
|
|
"driver": driver,
|
|
}), err)
|
|
}
|
|
return wrapDatabaseConnectionOpenError(err)
|
|
}
|
|
|
|
func isLikelySystemODBCDriverName(driver string) bool {
|
|
normalized := strings.ToLower(strings.TrimSpace(driver))
|
|
return strings.Contains(normalized, "odbc") ||
|
|
strings.Contains(normalized, "jdbc") ||
|
|
strings.Contains(normalized, "intersystems") ||
|
|
strings.Contains(normalized, "iris")
|
|
}
|
|
|
|
func (c *CustomDB) Close() error {
|
|
if c.conn != nil {
|
|
return c.conn.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *CustomDB) Ping() error {
|
|
if c.conn == nil {
|
|
return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
timeout := c.pingTimeout
|
|
if timeout <= 0 {
|
|
timeout = 5 * time.Second
|
|
}
|
|
ctx, cancel := utils.ContextWithTimeout(timeout)
|
|
defer cancel()
|
|
return c.conn.PingContext(ctx)
|
|
}
|
|
|
|
func (c *CustomDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) {
|
|
if c.conn == nil {
|
|
return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
|
|
rows, err := c.conn.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
return scanRowsForDialect(rows, c.scanDialect())
|
|
}
|
|
|
|
func (c *CustomDB) Query(query string) ([]map[string]interface{}, []string, error) {
|
|
if c.conn == nil {
|
|
return nil, nil, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
|
|
rows, err := c.conn.Query(query)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
return scanRowsForDialect(rows, c.scanDialect())
|
|
}
|
|
|
|
func (c *CustomDB) StreamQueryContext(ctx context.Context, query string, consumer QueryStreamConsumer) error {
|
|
if c.conn == nil {
|
|
return fmt.Errorf("连接未打开")
|
|
}
|
|
|
|
rows, err := c.conn.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
return streamRowsForDialect(rows, c.scanDialect(), consumer)
|
|
}
|
|
|
|
func (c *CustomDB) StreamQuery(query string, consumer QueryStreamConsumer) error {
|
|
return c.StreamQueryContext(context.Background(), query, consumer)
|
|
}
|
|
|
|
func (c *CustomDB) scanDialect() string {
|
|
if strings.EqualFold(strings.TrimSpace(c.driver), "mysql") {
|
|
return "mysql"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (c *CustomDB) ExecContext(ctx context.Context, query string) (int64, error) {
|
|
if c.conn == nil {
|
|
return 0, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
res, err := c.conn.ExecContext(ctx, query)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (c *CustomDB) Exec(query string) (int64, error) {
|
|
if c.conn == nil {
|
|
return 0, localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
res, err := c.conn.Exec(query)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|
|
|
|
func (c *CustomDB) GetDatabases() ([]string, error) {
|
|
// Try standard information_schema or some known patterns if we can't guess
|
|
// For "custom", we can't easily know.
|
|
// But many DBs support SHOW DATABASES or SELECT datname FROM pg_database
|
|
// We'll try a generic query or return empty.
|
|
// Users using custom might know their DB context is single.
|
|
|
|
if c.driver == "mysql" {
|
|
data, _, err := c.Query("SHOW DATABASES")
|
|
if err == nil {
|
|
var dbs []string
|
|
for _, row := range data {
|
|
for _, v := range row {
|
|
name := strings.TrimSpace(fmt.Sprintf("%v", v))
|
|
if name != "" {
|
|
dbs = append(dbs, name)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if len(dbs) > 0 {
|
|
return dbs, nil
|
|
}
|
|
}
|
|
|
|
// Fallback for restricted accounts: at least expose current database.
|
|
data, _, fallbackErr := c.Query("SELECT DATABASE() AS database_name")
|
|
if fallbackErr == nil {
|
|
for _, row := range data {
|
|
for _, v := range row {
|
|
name := strings.TrimSpace(fmt.Sprintf("%v", v))
|
|
if name != "" && !strings.EqualFold(name, "<nil>") && !strings.EqualFold(name, "null") {
|
|
return []string{name}, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Best effort:
|
|
return []string{}, nil
|
|
}
|
|
|
|
func (c *CustomDB) GetTables(dbName string) ([]string, error) {
|
|
// ANSI Standard
|
|
query := "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"
|
|
// If mysql-like
|
|
if c.driver == "mysql" {
|
|
query = "SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME"
|
|
if dbName != "" {
|
|
query = fmt.Sprintf(
|
|
"SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA = '%s' AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME",
|
|
strings.ReplaceAll(dbName, "'", "''"),
|
|
)
|
|
}
|
|
} else if c.driver == "postgres" || c.driver == "kingbase" {
|
|
query = `
|
|
SELECT table_schema AS schemaname, table_name AS tablename
|
|
FROM information_schema.tables
|
|
WHERE table_type = 'BASE TABLE'
|
|
AND table_schema NOT IN ('pg_catalog', 'information_schema')`
|
|
if dbName != "" {
|
|
query += fmt.Sprintf(" AND table_schema = '%s'", dbName)
|
|
}
|
|
query += " ORDER BY table_schema, table_name"
|
|
} else if c.driver == "sqlite" {
|
|
query = "SELECT name FROM sqlite_master WHERE type='table'"
|
|
} else if c.driver == "oracle" || c.driver == "dm" {
|
|
query = "SELECT table_name FROM user_tables"
|
|
if dbName != "" {
|
|
query = fmt.Sprintf("SELECT owner, table_name FROM all_tables WHERE owner = '%s' ORDER BY table_name", strings.ToUpper(dbName))
|
|
}
|
|
}
|
|
|
|
// Fallback generic execution
|
|
data, _, err := c.Query(query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get tables for custom driver %s: %v", c.driver, err)
|
|
}
|
|
|
|
var tables []string
|
|
for _, row := range data {
|
|
if schema, okSchema := row["schemaname"]; okSchema {
|
|
if name, okName := row["tablename"]; okName {
|
|
tables = append(tables, fmt.Sprintf("%v.%v", schema, name))
|
|
continue
|
|
}
|
|
}
|
|
if owner, okOwner := row["OWNER"]; okOwner {
|
|
if name, okName := row["TABLE_NAME"]; okName {
|
|
tables = append(tables, fmt.Sprintf("%v.%v", owner, name))
|
|
continue
|
|
}
|
|
}
|
|
// iterate keys to find likely column
|
|
for k, v := range row {
|
|
if strings.Contains(strings.ToLower(k), "name") || strings.Contains(strings.ToLower(k), "table") {
|
|
tables = append(tables, fmt.Sprintf("%v", v))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return tables, nil
|
|
}
|
|
|
|
func (c *CustomDB) GetCreateStatement(dbName, tableName string) (string, error) {
|
|
return "Not supported for custom connections yet", nil
|
|
}
|
|
|
|
func (c *CustomDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
|
|
// ANSI Standard
|
|
// SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_name = '...'
|
|
|
|
schema := "public"
|
|
if dbName != "" {
|
|
schema = dbName
|
|
}
|
|
|
|
query := fmt.Sprintf(`SELECT column_name, data_type, character_maximum_length, numeric_precision, numeric_scale, is_nullable, column_default
|
|
FROM information_schema.columns
|
|
WHERE table_name = '%s'`, tableName)
|
|
|
|
// Adjust for schema if likely supported
|
|
if c.driver == "postgres" || c.driver == "kingbase" {
|
|
query += fmt.Sprintf(" AND table_schema = '%s'", schema)
|
|
} else if c.driver == "mysql" {
|
|
query = fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`", tableName)
|
|
if dbName != "" {
|
|
query = fmt.Sprintf("SHOW FULL COLUMNS FROM `%s`.`%s`", dbName, tableName)
|
|
}
|
|
}
|
|
|
|
data, _, err := c.Query(query)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var columns []connection.ColumnDefinition
|
|
for _, row := range data {
|
|
columns = append(columns, buildCustomColumnDefinition(row))
|
|
}
|
|
return columns, nil
|
|
}
|
|
|
|
func buildCustomColumnDefinition(row map[string]interface{}) connection.ColumnDefinition {
|
|
col := connection.ColumnDefinition{
|
|
Name: customMetadataString(row, "Field", "field", "COLUMN_NAME", "column_name", "NAME", "name"),
|
|
Type: buildCustomColumnType(row),
|
|
Nullable: normalizeCustomNullable(customMetadataString(row, "Null", "null", "IS_NULLABLE", "is_nullable", "NULLABLE", "nullable")),
|
|
Key: customMetadataString(row, "Key", "key", "COLUMN_KEY", "column_key", "PRIMARY_KEY", "primary_key"),
|
|
Extra: customMetadataString(row, "Extra", "extra", "EXTRA"),
|
|
Comment: customMetadataString(row, "Comment", "comment", "COMMENTS", "comments", "COLUMN_COMMENT", "column_comment"),
|
|
}
|
|
if defaultValue, ok := customMetadataStringOK(row, "Default", "default", "COLUMN_DEFAULT", "column_default", "DATA_DEFAULT", "data_default"); ok {
|
|
col.Default = &defaultValue
|
|
}
|
|
return col
|
|
}
|
|
|
|
func buildCustomColumnType(row map[string]interface{}) string {
|
|
rawType := customMetadataString(
|
|
row,
|
|
"COLUMN_TYPE",
|
|
"column_type",
|
|
"FULL_TYPE",
|
|
"full_type",
|
|
"FULL_DATA_TYPE",
|
|
"full_data_type",
|
|
"TYPE_NAME",
|
|
"type_name",
|
|
"Type",
|
|
"type",
|
|
"DATA_TYPE",
|
|
"data_type",
|
|
)
|
|
if rawType == "" || strings.Contains(rawType, "(") {
|
|
return rawType
|
|
}
|
|
|
|
upperType := strings.ToUpper(rawType)
|
|
charLength := customMetadataInt(row, "CHARACTER_MAXIMUM_LENGTH", "character_maximum_length", "CHARACTER_MAX_LENGTH", "character_max_length", "CHAR_LENGTH", "char_length", "LENGTH", "length")
|
|
if charLength > 0 && strings.Contains(upperType, "CHAR") {
|
|
return fmt.Sprintf("%s(%d)", rawType, charLength)
|
|
}
|
|
|
|
precision := customMetadataInt(row, "NUMERIC_PRECISION", "numeric_precision", "DATA_PRECISION", "data_precision", "PRECISION", "precision")
|
|
if precision > 0 && (strings.Contains(upperType, "DECIMAL") || strings.Contains(upperType, "NUMERIC") || strings.Contains(upperType, "NUMBER")) {
|
|
scale := customMetadataInt(row, "NUMERIC_SCALE", "numeric_scale", "DATA_SCALE", "data_scale", "SCALE", "scale")
|
|
if scale > 0 {
|
|
return fmt.Sprintf("%s(%d,%d)", rawType, precision, scale)
|
|
}
|
|
return fmt.Sprintf("%s(%d)", rawType, precision)
|
|
}
|
|
|
|
return rawType
|
|
}
|
|
|
|
func customMetadataString(row map[string]interface{}, keys ...string) string {
|
|
value, _ := customMetadataStringOK(row, keys...)
|
|
return value
|
|
}
|
|
|
|
func customMetadataStringOK(row map[string]interface{}, keys ...string) (string, bool) {
|
|
for _, key := range keys {
|
|
for rowKey, raw := range row {
|
|
if !strings.EqualFold(rowKey, key) || raw == nil {
|
|
continue
|
|
}
|
|
return strings.TrimSpace(fmt.Sprintf("%v", raw)), true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func customMetadataInt(row map[string]interface{}, keys ...string) int {
|
|
return parseMetadataInt(customMetadataString(row, keys...))
|
|
}
|
|
|
|
func normalizeCustomNullable(value string) string {
|
|
trimmed := strings.TrimSpace(value)
|
|
switch strings.ToLower(trimmed) {
|
|
case "n", "no", "false", "0":
|
|
return "NO"
|
|
case "y", "yes", "true", "1":
|
|
return "YES"
|
|
default:
|
|
return trimmed
|
|
}
|
|
}
|
|
|
|
func (c *CustomDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
|
|
return nil, fmt.Errorf("not implemented for custom")
|
|
}
|
|
|
|
func (c *CustomDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {
|
|
return nil, fmt.Errorf("not implemented for custom")
|
|
}
|
|
|
|
func (c *CustomDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) {
|
|
return nil, fmt.Errorf("not implemented for custom")
|
|
}
|
|
|
|
func (c *CustomDB) ApplyChanges(tableName string, changes connection.ChangeSet) error {
|
|
if c.conn == nil {
|
|
return localizedDatabaseRuntimeError("db.backend.error.connection_not_open", nil)
|
|
}
|
|
|
|
tx, err := c.conn.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
driver := strings.ToLower(strings.TrimSpace(c.driver))
|
|
isMySQL := strings.Contains(driver, "mysql")
|
|
isKingbase := strings.Contains(driver, "kingbase")
|
|
isPostgres := strings.Contains(driver, "postgres") || isKingbase || strings.Contains(driver, "pg")
|
|
isOracle := strings.Contains(driver, "oracle") || strings.Contains(driver, "ora") || strings.Contains(driver, "dm") || strings.Contains(driver, "dameng")
|
|
isSQLServer := strings.Contains(driver, "sqlserver") || strings.Contains(driver, "mssql")
|
|
isSQLite := strings.Contains(driver, "sqlite") || strings.Contains(driver, "duckdb")
|
|
|
|
quoteIdent := func(name string) string {
|
|
n := strings.TrimSpace(name)
|
|
if isKingbase {
|
|
return QuoteKingbaseIdentifier(n)
|
|
}
|
|
if isMySQL {
|
|
n = strings.Trim(n, "`")
|
|
n = strings.ReplaceAll(n, "`", "``")
|
|
if n == "" {
|
|
return "``"
|
|
}
|
|
return "`" + n + "`"
|
|
}
|
|
n = strings.Trim(n, "\"")
|
|
n = strings.ReplaceAll(n, "\"", "\"\"")
|
|
if n == "" {
|
|
return "\"\""
|
|
}
|
|
return `"` + n + `"`
|
|
}
|
|
|
|
placeholder := func(idx int) string {
|
|
if isPostgres {
|
|
return fmt.Sprintf("$%d", idx)
|
|
}
|
|
if isOracle {
|
|
return fmt.Sprintf(":%d", idx)
|
|
}
|
|
// MySQL / SQLite / default
|
|
return "?"
|
|
}
|
|
|
|
schema := ""
|
|
table := strings.TrimSpace(tableName)
|
|
if isKingbase {
|
|
schema, table = SplitKingbaseQualifiedName(table)
|
|
} else if parts := strings.SplitN(table, ".", 2); len(parts) == 2 {
|
|
schema = strings.TrimSpace(parts[0])
|
|
table = strings.TrimSpace(parts[1])
|
|
}
|
|
|
|
qualifiedTable := ""
|
|
if schema != "" {
|
|
qualifiedTable = fmt.Sprintf("%s.%s", quoteIdent(schema), quoteIdent(table))
|
|
} else {
|
|
qualifiedTable = quoteIdent(table)
|
|
}
|
|
|
|
// 1. Deletes
|
|
for _, pk := range changes.Deletes {
|
|
var wheres []string
|
|
var args []interface{}
|
|
idx := 0
|
|
for k, v := range pk {
|
|
idx++
|
|
wheres = append(wheres, fmt.Sprintf("%s = %s", quoteIdent(k), placeholder(idx)))
|
|
args = append(args, v)
|
|
}
|
|
if len(wheres) == 0 {
|
|
continue
|
|
}
|
|
query := fmt.Sprintf("DELETE FROM %s WHERE %s", qualifiedTable, strings.Join(wheres, " AND "))
|
|
if _, err := tx.Exec(query, args...); err != nil {
|
|
return localizedDatabaseRuntimeError("db.backend.error.row_delete_failed", map[string]any{"detail": err.Error()})
|
|
}
|
|
}
|
|
|
|
// 2. Updates
|
|
for _, update := range changes.Updates {
|
|
var sets []string
|
|
var args []interface{}
|
|
idx := 0
|
|
|
|
for k, v := range update.Values {
|
|
idx++
|
|
sets = append(sets, fmt.Sprintf("%s = %s", quoteIdent(k), placeholder(idx)))
|
|
args = append(args, v)
|
|
}
|
|
|
|
if len(sets) == 0 {
|
|
continue
|
|
}
|
|
|
|
var wheres []string
|
|
for k, v := range update.Keys {
|
|
idx++
|
|
wheres = append(wheres, fmt.Sprintf("%s = %s", quoteIdent(k), placeholder(idx)))
|
|
args = append(args, v)
|
|
}
|
|
|
|
if len(wheres) == 0 {
|
|
return localizedDatabaseRuntimeError("db.backend.error.row_update_key_conditions_required", nil)
|
|
}
|
|
|
|
query := fmt.Sprintf("UPDATE %s SET %s WHERE %s", qualifiedTable, strings.Join(sets, ", "), strings.Join(wheres, " AND "))
|
|
if _, err := tx.Exec(query, args...); err != nil {
|
|
return localizedDatabaseRuntimeError("db.backend.error.row_update_failed", map[string]any{"detail": err.Error()})
|
|
}
|
|
}
|
|
|
|
if err := execParameterizedInsertBatches(parameterizedInsertConfig{
|
|
Table: qualifiedTable,
|
|
Rows: changes.Inserts,
|
|
QuoteColumn: quoteIdent,
|
|
Placeholder: placeholder,
|
|
Exec: func(query string, args ...interface{}) (sql.Result, error) {
|
|
return tx.Exec(query, args...)
|
|
},
|
|
MaxArgs: customInsertMaxArgs(isSQLServer, isSQLite),
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func customInsertMaxArgs(isSQLServer, isSQLite bool) int {
|
|
switch {
|
|
case isSQLServer:
|
|
return sqlServerBatchInsertArgs
|
|
case isSQLite:
|
|
return sqliteBatchInsertArgs
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func (c *CustomDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) {
|
|
return nil, fmt.Errorf("not implemented for custom")
|
|
}
|