Skip to content

Commit

Permalink
sql: sanitize the "no current db" story
Browse files Browse the repository at this point in the history
Prior to this patch, CockroachdB made it excessively simple for client
connections to not have any current database set. Also by design
CockroachDB allows a connection to be opened with a non-existent
database set as current.

This in turn causes various problems related to name resolution and
the `pg_catalog` schema.

To understand these problems we can consider separately two groups of
users, which have separate use cases:

- newcomers to CockroachDB that don't have prior experience with SQL
  or PostgreSQL. These will not know about "databases" in any sense,
  and will use CockroachDB with default options. This includes using
  `cockroach sql` with no current database selected. These users have
  to be taught upfront that they need to "create a database" and "set
  the current database", which they may or may not (more likely not)
  be interested in.

  When these users then transition into writing apps, they will
  copy-paste the conn URL printed by `cockroach start` (which sets no
  current db) into some ORM or framework, and then will regretfully
  observe that their app fail in mysterious ways because the current
  db is not set (also see the next point).

- users of existing applications or SQL drivers that have been
  developed to target PostgreSQL. These apps commonly contain code
  that:

  - connect to a database named "`postgres`" by default, and make this
    default hard(er) to customize. For example Ecto (Elixir ORM)
    doesn't make this configurable.

    (From the PostgreSQL docs: "After initialization, a database
    cluster will contain a database named `postgres`, which is meant as
    a default database for use by utilities, users and third party
    applications. The database server itself does not require the
    postgres database to exist, but many external utility programs
    assume it exists.")

  - (regardless of which db is selected as current), will issue some
    introspection queries using `pg_catalog` before any additional
    initialization is made by the higher-level app code, in particular
    before the app can issue `CREATE DATABASE`. Currently `pg_catalog`
    queries (and several other things) fail if the current database is
    unset or set to a non-existing database.

To address this relatively large class of problems, this patch
modifies CockroachDB as follows:

- all clusters (existing or new) now get two empty databases on
  startup (created by a migration) called `default` and `postgres`.

- when a client doesn't specify a current db in their conn string
  (i.e. in the pgwire options), the `default` database is picked up
  instead.

This resolves all the known problems around the situations identified
above.

The two new databases behave like any regular database and are
provided for convenience to newcomers and
compatibility. (Administrators are free to drop them afterwards to
restore the previous situation, if that is actively desired.)

In addition, to make it slightly harder for newcomers to shoot
themselves in the foot, the `database` session variable cannot be set
to the empty string any more if `sql_safe_updates` is otherwise set.

Three alternatives to this approach were considered, discussed with
@jordanlewis and @nvanbenschoten in their quality of pg compatibility
experts, and with @bdarnell in his quality of carer for newcomers, and
ultimately rejected:

A) generate the current db upon connection if it does not exist, make
   connections using the empty string as curdb use `system` instead.

   Rejected because we don't like to auto-create upon connection. Too
   easy to flood the system with bogus database names by typos in the
   connection string.

B) Pre-populate clusters with a special anonymous database (db name =
   empty string).

   Rejected because there are too many areas in CockroachDB with a
   risk to fail silently or with weird error messages as a
   result. Also does not solve the problem of clients that want a db
   called `postgres`.

C) Auto-create a database named after the user upon user creation
   (with an optional flag to skip db creation).

   Rejected because it does not solve the problem of clients that want
   a db called `postgres`. @bdarnell: creating a database per user also
   implicitly encourages bad habits (like sharing certs for the user
   whose name corresponds to the database instead of creating multiple
   users).

D) Implement some magic in the handling of `pg_catalog`, name
   resolution and all other pieces that currently don't like non-existent
   `database` values to create the appearance of something that works.

   Rejected because too complex to implement successfully (there are
   many moving parts that need to be touched to make this work) and
   the resulting magic would be hard to understand by CockroachDB
   contributors and hard to maintain over time.

Release note (sql change): new and existing clusters will now contain
two empty databases called `default` and `postgres` by default. The
database `default` is automatically used for clients that connect
without a current database set (e.g. without a database component in
the connection URL). The database `postgres` is provided for
compatibility with PostgreSQL client frameworks that require it to
exist when the database server has ben freshly installed. Both new
databases behave like any other regular database.

Release note (general change): existing clusters upgraded to this
version of CockroachDB will automatically see two new empty databases
named `default` and `postgres`. These were added for compatibility
with PostgreSQL and to ease adoption of CockroachDB. They can be
manually deleted by an administrator if the client applications are
determined not to require them.
  • Loading branch information
knz committed Apr 12, 2018
1 parent b0e48e4 commit 1474923
Show file tree
Hide file tree
Showing 24 changed files with 558 additions and 440 deletions.
20 changes: 10 additions & 10 deletions pkg/ccl/logictestccl/testdata/logic_test/crdb_internal
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ CREATE table t2 (a STRING PRIMARY KEY) PARTITION BY LIST (a) (
query IITTI
SELECT * FROM crdb_internal.partitions ORDER BY table_id, index_id, name
----
51 1 NULL p12 1
51 1 p12 p12p3 1
51 1 p12p3 p12p3p8 1
51 1 NULL p6 1
51 1 p6 p6p7 1
51 1 p6 p6p8 1
51 1 p6 p6px 1
51 1 p12 pd 1
51 2 NULL p00 2
52 1 NULL pfoo 1
53 1 NULL p12 1
53 1 p12 p12p3 1
53 1 p12p3 p12p3p8 1
53 1 NULL p6 1
53 1 p6 p6p7 1
53 1 p6 p6p8 1
53 1 p6 p6px 1
53 1 p12 pd 1
53 2 NULL p00 2
54 1 NULL pfoo 1
12 changes: 6 additions & 6 deletions pkg/cli/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,12 @@ func (c *cliState) refreshDatabaseName() (string, bool) {
return "", false
}

if dbVal == "" {
// Attempt to be helpful to new users.
fmt.Fprintln(stderr, "warning: no current database set."+
" Use SET database = <dbname> to change, CREATE DATABASE to make a new database.")
}

dbName := formatVal(dbVal.(string),
false /* showPrintableUnicode */, false /* shownewLinesAndTabs */)

Expand All @@ -570,12 +576,6 @@ func preparePrompts(dbURL string) (promptPrefix, fullPrompt, continuePrompt stri
username = parsedURL.User.Username()
}
promptPrefix = fmt.Sprintf("%s@%s", username, parsedURL.Host)

if parsedURL.Path == "" {
// Attempt to be helpful to new users.
fmt.Fprintln(stderr, "warning: no current database set."+
" Use SET database = <dbname> to change, CREATE DATABASE to make a new database.")
}
}

if len(promptPrefix) == 0 {
Expand Down
9 changes: 7 additions & 2 deletions pkg/sql/conn_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,14 +411,19 @@ func (s *Server) ServeConn(
settings := &s.cfg.Settings.SV
distSQLMode := sessiondata.DistSQLExecMode(DistSQLClusterExecMode.Get(settings))

curDb := args.Database
if curDb == "" {
curDb = sessiondata.DefaultDatabaseName
}

ex := connExecutor{
server: s,
stmtBuf: stmtBuf,
clientComm: clientComm,
mon: &sessionRootMon,
sessionMon: &sessionMon,
sessionData: sessiondata.SessionData{
Database: args.Database,
Database: curDb,
DistSQLMode: distSQLMode,
SearchPath: sqlbase.DefaultSearchPath,
Location: time.UTC,
Expand Down Expand Up @@ -464,7 +469,7 @@ func (s *Server) ServeConn(
data: &ex.sessionData,
defaults: sessionDefaults{
applicationName: args.ApplicationName,
database: args.Database,
database: curDb,
},
settings: s.cfg.Settings,
curTxnReadOnly: &ex.state.readOnly,
Expand Down
8 changes: 4 additions & 4 deletions pkg/sql/logictest/testdata/logic_test/crdb_internal
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ statement ok
INSERT INTO system.zones (id, config) VALUES
(17, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0)),
(18, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0)),
(51, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0)),
(52, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0))
(53, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0)),
(54, (SELECT config_proto FROM crdb_internal.zones WHERE id = 0))

query IT
SELECT id, cli_specifier FROM crdb_internal.zones ORDER BY id
Expand All @@ -212,8 +212,8 @@ SELECT id, cli_specifier FROM crdb_internal.zones ORDER BY id
17 .system
18 .timeseries
22 .liveness
51 testdb
52 testdb.foo
53 testdb
54 testdb.foo

query error pq: foo
SELECT crdb_internal.force_error('', 'foo')
Expand Down
6 changes: 6 additions & 0 deletions pkg/sql/logictest/testdata/logic_test/database
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ SHOW DATABASES
----
Database
a
default
postgres
system
test

Expand Down Expand Up @@ -85,6 +87,8 @@ b4
b5
b6
c
default
postgres
system
test

Expand Down Expand Up @@ -132,6 +136,8 @@ SHOW DATABASES
Database
a
c
default
postgres
system
test

Expand Down
8 changes: 4 additions & 4 deletions pkg/sql/logictest/testdata/logic_test/deep_interleaving
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ EXPLAIN SELECT * FROM level4 WHERE k1 > 1 AND k1 < 3
----
scan · ·
· table level4@primary
· spans /2/#/52/1/#/53/1-/2/#/52/1/#/53/2
· spans /2/#/54/1/#/55/1-/2/#/54/1/#/55/2

query III rowsort
SELECT * FROM level4 WHERE k1 > 1 AND k1 < 3
Expand All @@ -131,7 +131,7 @@ EXPLAIN SELECT * FROM level4 WHERE k1 = 2 AND k2 > 10 AND k2 < 30
----
scan · ·
· table level4@primary
· spans /2/#/52/1/#/53/1/11-/2/#/52/1/#/53/1/30
· spans /2/#/54/1/#/55/1/11-/2/#/54/1/#/55/1/30

query III rowsort
SELECT * FROM level4 WHERE k1 = 2 AND k2 > 10 AND k2 < 30
Expand All @@ -145,7 +145,7 @@ EXPLAIN SELECT * FROM level4 WHERE k1 = 2 AND k2 = 20 AND k3 > 100 AND k3 < 300
----
scan · ·
· table level4@primary
· spans /2/#/52/1/#/53/1/20/101/#/54/1-/2/#/52/1/#/53/1/20/299/#/54/1/#
· spans /2/#/54/1/#/55/1/20/101/#/56/1-/2/#/54/1/#/55/1/20/299/#/56/1/#

query III
SELECT * FROM level4 WHERE k1 = 2 AND k2 = 20 AND k3 > 100 AND k3 < 300
Expand All @@ -157,7 +157,7 @@ EXPLAIN SELECT * FROM level4 WHERE k1 = 2 AND k2 = 20 AND k3 = 200
----
scan · ·
· table level4@primary
· spans /2/#/52/1/#/53/1/20/200/#/54/1-/2/#/52/1/#/53/1/20/200/#/54/1/#
· spans /2/#/54/1/#/55/1/20/200/#/56/1-/2/#/54/1/#/55/1/20/200/#/56/1/#

query III
SELECT * FROM level4 WHERE k1 = 2 AND k2 = 20 AND k3 = 200
Expand Down
Loading

0 comments on commit 1474923

Please sign in to comment.