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
2 changes: 2 additions & 0 deletions server/analyzer/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
ruleId_OptimizeFunctions // optimizeFunctions
ruleId_ValidateColumnDefaults // validateColumnDefaults
ruleId_ValidateCreateTable // validateCreateTable
ruleId_ValidateCreateSchema // validateCreateSchema
ruleId_ResolveAlterColumn // resolveAlterColumn
ruleId_ValidateCreateFunction
)
Expand All @@ -62,6 +63,7 @@ func Init() {
analyzer.Rule{Id: ruleId_AssignUpdateCasts, Apply: AssignUpdateCasts},
analyzer.Rule{Id: ruleId_AssignTriggers, Apply: AssignTriggers},
analyzer.Rule{Id: ruleId_ValidateCreateFunction, Apply: ValidateCreateFunction},
analyzer.Rule{Id: ruleId_ValidateCreateSchema, Apply: ValidateCreateSchema},
)

analyzer.OnceBeforeDefault = append([]analyzer.Rule{
Expand Down
54 changes: 54 additions & 0 deletions server/analyzer/validate_create_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package analyzer

import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/analyzer"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/transform"

"github.com/dolthub/doltgresql/core"
)

// ValidateCreateSchema validates that CREATE SCHEMA is executed with a valid database context.
// In PostgreSQL, schemas exist within databases, so CREATE SCHEMA requires an active database.
// See: https://github.com/dolthub/doltgresql/issues/1863
func ValidateCreateSchema(
ctx *sql.Context,
a *analyzer.Analyzer,
n sql.Node,
scope *plan.Scope,
sel analyzer.RuleSelector,
qFlags *sql.QueryFlags,
) (sql.Node, transform.TreeIdentity, error) {
cs, ok := n.(*plan.CreateSchema)
if !ok {
return n, transform.SameTree, nil
}

// Check if the current database actually has a working root.
// GetRootFromContext will return sql.ErrDatabaseNotFound if the database
// is not properly initialized.
_, _, err := core.GetRootFromContext(ctx)
if err != nil {
if sql.ErrDatabaseNotFound.Is(err) {
return nil, transform.SameTree, sql.ErrNoDatabaseSelected.New()
}
return nil, transform.SameTree, err
}

return cs, transform.SameTree, nil
}
30 changes: 29 additions & 1 deletion testing/bats/doltgres.bats
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,35 @@ query_server_for_user_and_pass() {
shift
shift
shift

nativevar PGPASSWORD "$pass" /w
psql -U "$user" -h localhost -p $PORT "$@" $db
}

# Test for https://github.com/dolthub/doltgresql/issues/1863
@test 'doltgres: connection to non-existent database fails' {
start_sql_server

# Connecting to the default postgres database should work
run query_server -c "SELECT 1"
[ "$status" -eq 0 ]

# Connecting to a non-existent database should fail
nativevar PGPASSWORD "password" /w
run psql -U postgres -h localhost -p $PORT -c "SELECT 1" nonexistent_db
[ "$status" -ne 0 ]
[[ "$output" =~ "does not exist" ]] || false
}

@test 'doltgres: CREATE SCHEMA works on valid database' {
start_sql_server

# CREATE SCHEMA should work on a valid database
run query_server -c "CREATE SCHEMA test_schema_bats"
[ "$status" -eq 0 ]

# Verify schema was created
run query_server -c "SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'test_schema_bats'"
[ "$status" -eq 0 ]
[[ "$output" =~ "test_schema_bats" ]] || false
}
107 changes: 107 additions & 0 deletions testing/go/create_schema_no_db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package _go

import (
"context"
"fmt"
"testing"

"github.com/dolthub/go-mysql-server/sql"
"github.com/jackc/pgx/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

dserver "github.com/dolthub/doltgresql/server"
"github.com/dolthub/doltgresql/servercfg"
"github.com/dolthub/doltgresql/servercfg/cfgdetails"
)

// TestCreateSchemaWithNonExistentDatabase tests that connecting to a non-existent
// database fails with the appropriate error at the connection level.
// This is the PostgreSQL-compliant behavior where database validation happens
// during connection establishment.
//
// NOTE: This test cannot use RunScripts because RunScripts auto-creates the
// database before running assertions. We need to test the connection-level
// failure when connecting to a non-existent database.
//
// See: https://github.com/dolthub/doltgresql/issues/1863
func TestCreateSchemaWithNonExistentDatabase(t *testing.T) {
port, err := sql.GetEmptyPort()
require.NoError(t, err)

// Start server using the same pattern as CreateServerWithPort
controller, err := dserver.RunInMemory(
&servercfg.DoltgresConfig{
DoltgresConfig: cfgdetails.DoltgresConfig{
ListenerConfig: &cfgdetails.DoltgresListenerConfig{
PortNumber: &port,
HostStr: &serverHost,
},
LogLevelStr: &testServerLogLevel,
},
}, dserver.NewListener,
)
require.NoError(t, err)
defer func() {
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()

ctx := context.Background()

t.Run(
"connection to non-existent database fails", func(t *testing.T) {
// Attempt to connect to a database that doesn't exist
connStr := fmt.Sprintf(
"postgres://postgres:password@%s:%d/nonexistent_db?sslmode=disable",
serverHost,
port,
)
_, err := pgx.Connect(ctx, connStr)

require.Error(t, err, "connection should fail when database doesn't exist")
assert.Contains(
t, err.Error(), "does not exist",
"expected 'does not exist' error, got: %v", err,
)
},
)

t.Run(
"connection to existing database succeeds", func(t *testing.T) {
// Verify that connecting to the default "postgres" database works
connStr := fmt.Sprintf("postgres://postgres:password@%s:%d/postgres?sslmode=disable", serverHost, port)
conn, err := pgx.Connect(ctx, connStr)
require.NoError(t, err)
defer conn.Close(ctx)

// Verify we can create a schema on the valid database
_, err = conn.Exec(ctx, "CREATE SCHEMA test_schema_1863")
require.NoError(t, err)

// Verify the schema was created
rows, err := conn.Query(
ctx,
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'test_schema_1863'",
)
require.NoError(t, err)
defer rows.Close()

require.True(t, rows.Next(), "expected to find test_schema_1863")
},
)
}