-
Notifications
You must be signed in to change notification settings - Fork 51
feat: add support for custom template databases #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,8 @@ type ( | |
| lockTimeoutModifiers []string | ||
| insertStatements []string | ||
| outputFormat outputFormat | ||
|
|
||
| templateDb string | ||
| } | ||
|
|
||
| timeoutModifier struct { | ||
|
|
@@ -117,6 +119,8 @@ type ( | |
| statementTimeoutModifiers []timeoutModifier | ||
| lockTimeoutModifiers []timeoutModifier | ||
| insertStatements []insertStatement | ||
|
|
||
| templateDb string | ||
| } | ||
| ) | ||
|
|
||
|
|
@@ -173,6 +177,13 @@ func createPlanFlags(cmd *cobra.Command) *planFlags { | |
|
|
||
| cmd.Flags().Var(&flags.outputFormat, "output-format", "Change the output format for what is printed. Defaults to pretty-printed human-readable output. (options: pretty, json)") | ||
|
|
||
| cmd.Flags().StringVar( | ||
| &flags.templateDb, | ||
| "template-db", | ||
| "template0", | ||
| "Template database to use when creating temporary databases", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add to this doc string: "This is used to derive implicit schema objects for the target schema" |
||
| ) | ||
|
|
||
| return flags | ||
| } | ||
|
|
||
|
|
@@ -250,6 +261,7 @@ func parsePlanConfig(p planFlags) (planConfig, error) { | |
| statementTimeoutModifiers: statementTimeoutModifiers, | ||
| lockTimeoutModifiers: lockTimeoutModifiers, | ||
| insertStatements: insertStatements, | ||
| templateDb: p.templateDb, | ||
| }, nil | ||
| } | ||
|
|
||
|
|
@@ -384,7 +396,7 @@ func generatePlan(ctx context.Context, logger log.Logger, connConfig *pgx.ConnCo | |
| copiedConfig := connConfig.Copy() | ||
| copiedConfig.Database = dbName | ||
| return openDbWithPgxConfig(copiedConfig) | ||
| }, tempdb.WithRootDatabase(connConfig.Database)) | ||
| }, tempdb.WithRootDatabase(connConfig.Database), tempdb.WithTemplateDatabase(planConfig.templateDb)) | ||
| if err != nil { | ||
| return diff.Plan{}, err | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import ( | |
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
@@ -32,11 +33,11 @@ func TestParseTimeoutModifierStr(t *testing.T) { | |
| }, | ||
| { | ||
| opt: "timeout=15m", | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "pattern" missing | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we remove these. I understand why they were included, but I prefer not to have these comments, since they might quickly become outdated |
||
| }, | ||
| { | ||
| opt: `pattern="some pattern"`, | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "timeout" missing | ||
| }, | ||
| { | ||
| opt: `pattern="normal" timeout=5m some-unknown-key=5m`, | ||
|
|
@@ -80,19 +81,19 @@ func TestParseInsertStatementStr(t *testing.T) { | |
| }, | ||
| { | ||
| opt: "statement=no-index timeout=5m6s lock_timeout=1m11s", | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "index" missing | ||
| }, | ||
| { | ||
| opt: "index=0 timeout=5m6s lock_timeout=1m11s", | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "statement" missing | ||
| }, | ||
| { | ||
| opt: "index=0 statement=no-timeout lock_timeout=1m11s", | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "timeout" missing | ||
| }, | ||
| { | ||
| opt: "index=0 statement=no-lock-timeout-timeout timeout=5m6s", | ||
| expectedErrContains: "could not find key", | ||
| expectedErrContains: "could not find key", // "lock_timeout" missing | ||
| }, | ||
| { | ||
| opt: "index=not-an-int statement=some-statement timeout=5m6s lock_timeout=1m11s", | ||
|
|
@@ -118,3 +119,34 @@ func TestParseInsertStatementStr(t *testing.T) { | |
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestPlanFlagsTemplateDBDefault(t *testing.T) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably okay to not include these, since we don't have analog tests. The functionality it is testing is pretty straightforward too, i.e., flag parsing |
||
| cmd := &cobra.Command{} | ||
| flags := createPlanFlags(cmd) | ||
|
|
||
| err := cmd.ParseFlags([]string{ | ||
| "--schema-dir=/no/such/dir", | ||
| }) | ||
| require.NoError(t, err) | ||
|
|
||
| planCfg, err := parsePlanConfig(*flags) | ||
| require.NoError(t, err, "parsePlanConfig should not fail with a dummy --schema-dir") | ||
|
|
||
| assert.Equal(t, "template0", planCfg.templateDb) | ||
| } | ||
|
|
||
| func TestPlanFlagsTemplateDBOverride(t *testing.T) { | ||
| cmd := &cobra.Command{} | ||
| flags := createPlanFlags(cmd) | ||
|
|
||
| err := cmd.ParseFlags([]string{ | ||
| "--template-db=template1", | ||
| "--schema-dir=/no/such/dir", | ||
| }) | ||
| require.NoError(t, err) | ||
|
|
||
| planCfg, err := parsePlanConfig(*flags) | ||
| require.NoError(t, err, "parsePlanConfig should not fail with dummy --schema-dir") | ||
|
|
||
| assert.Equal(t, "template1", planCfg.templateDb) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ const ( | |
| DefaultOnInstanceMetadataSchema = "pgschemadiff_tmp_metadata" | ||
| DefaultOnInstanceMetadataTable = "metadata" | ||
|
|
||
| DefaultTemplateDatabase = "template0" | ||
| DefaultStatementTimeout = 3 * time.Second | ||
| ) | ||
|
|
||
|
|
@@ -57,11 +58,12 @@ type ( | |
| ) | ||
| type ( | ||
| onInstanceFactoryOptions struct { | ||
| dbPrefix string | ||
| metadataSchema string | ||
| metadataTable string | ||
| logger log.Logger | ||
| rootDatabase string | ||
| dbPrefix string | ||
| metadataSchema string | ||
| metadataTable string | ||
| logger log.Logger | ||
| rootDatabase string | ||
| templateDatabase string | ||
| } | ||
|
|
||
| OnInstanceFactoryOpt func(*onInstanceFactoryOptions) | ||
|
|
@@ -102,6 +104,13 @@ func WithRootDatabase(db string) OnInstanceFactoryOpt { | |
| } | ||
| } | ||
|
|
||
| // WithTemplateDatabase sets the template DB that CREATE DATABASE will use. | ||
| func WithTemplateDatabase(templateDB string) OnInstanceFactoryOpt { | ||
| return func(opts *onInstanceFactoryOptions) { | ||
| opts.templateDatabase = templateDB | ||
| } | ||
| } | ||
|
|
||
| type ( | ||
| CreateConnPoolForDbFn func(ctx context.Context, dbName string) (*sql.DB, error) | ||
|
|
||
|
|
@@ -126,11 +135,12 @@ type ( | |
| // when the temporary database was created, e.g., to create a TTL | ||
| func NewOnInstanceFactory(ctx context.Context, createConnPoolForDb CreateConnPoolForDbFn, opts ...OnInstanceFactoryOpt) (_ Factory, _retErr error) { | ||
| options := onInstanceFactoryOptions{ | ||
| dbPrefix: DefaultOnInstanceDbPrefix, | ||
| metadataSchema: DefaultOnInstanceMetadataSchema, | ||
| metadataTable: DefaultOnInstanceMetadataTable, | ||
| rootDatabase: "postgres", | ||
| logger: log.SimpleLogger(), | ||
| dbPrefix: DefaultOnInstanceDbPrefix, | ||
| metadataSchema: DefaultOnInstanceMetadataSchema, | ||
| metadataTable: DefaultOnInstanceMetadataTable, | ||
| rootDatabase: "postgres", | ||
| logger: log.SimpleLogger(), | ||
| templateDatabase: DefaultTemplateDatabase, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of making this a parameter of the factory, let's make this a parameter of the |
||
| } | ||
| for _, opt := range opts { | ||
| opt(&options) | ||
|
|
@@ -175,8 +185,15 @@ func (o *onInstanceFactory) Create(ctx context.Context) (_ *Database, _retErr er | |
| defer rootConn.Close() | ||
|
|
||
| tempDbName := o.options.dbPrefix + strings.ReplaceAll(dbUUID.String(), "-", "_") | ||
|
|
||
| createDbSql := fmt.Sprintf( | ||
| "CREATE DATABASE %s TEMPLATE %s;", | ||
| tempDbName, | ||
| pgx.Identifier{o.options.templateDatabase}.Sanitize(), | ||
| ) | ||
|
|
||
| // Create the temporary database using template0, the default Postgres template with no user-defined objects. | ||
| if _, err = rootConn.ExecContext(ctx, fmt.Sprintf("CREATE DATABASE %s TEMPLATE template0;", tempDbName)); err != nil { | ||
| if _, err = rootConn.ExecContext(ctx, createDbSql); err != nil { | ||
| return nil, fmt.Errorf("creating temporary database: %w", err) | ||
| } | ||
| defer util.DoOnErrOrPanic(&_retErr, func() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: name
schema-template-db