Skip to content
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

Resolve issues in UPDATE and DELETE when using schemas #618

Merged
merged 4 commits into from Feb 6, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/tests.yml
Expand Up @@ -110,12 +110,12 @@ jobs:
run: |
mkdir -p crdb/certs
pushd crdb
wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.0.linux-amd64.tgz | tar zxv
mv cockroach-v2.1.0.linux-amd64/cockroach .
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.2.4.linux-amd64.tgz | tar zxv
mv cockroach-v20.2.4.linux-amd64/cockroach .
./cockroach cert create-ca --certs-dir certs --ca-key key
./cockroach cert create-client root --certs-dir certs --ca-key key
./cockroach cert create-node localhost 127.0.0.1 `hostname -s` `hostname -f` --certs-dir certs --ca-key key
./cockroach start --certs-dir certs --listen-addr localhost --port 26259 --http-port 8089 --background
./cockroach start-single-node --certs-dir certs --listen-addr localhost --port 26259 --http-port 8089 --background
popd
- name: Build and run soda
env:
Expand Down Expand Up @@ -152,9 +152,9 @@ jobs:
run: |
mkdir -p crdb
pushd crdb
wget -qO- https://binaries.cockroachdb.com/cockroach-v2.1.0.linux-amd64.tgz | tar zxv
mv cockroach-v2.1.0.linux-amd64/cockroach .
./cockroach start --insecure --background
wget -qO- https://binaries.cockroachdb.com/cockroach-v20.2.4.linux-amd64.tgz | tar zxv
mv cockroach-v20.2.4.linux-amd64/cockroach .
./cockroach start-single-node --insecure --background
popd
- name: Build and run soda
env:
Expand Down
2 changes: 1 addition & 1 deletion dialect_cockroach.go
Expand Up @@ -105,7 +105,7 @@ func (p *cockroach) Update(s store, model *Model, cols columns.Columns) error {
}

func (p *cockroach) Destroy(s store, model *Model) error {
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s WHERE %s", p.Quote(model.TableName()), model.whereID()))
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", p.Quote(model.TableName()), model.alias(), model.whereID()))
_, err := genericExec(s, stmt, model.ID())
return err
}
Expand Down
4 changes: 2 additions & 2 deletions dialect_common.go
Expand Up @@ -99,7 +99,7 @@ func genericCreate(s store, model *Model, cols columns.Columns, quoter quotable)
}

func genericUpdate(s store, model *Model, cols columns.Columns, quoter quotable) error {
stmt := fmt.Sprintf("UPDATE %s SET %s WHERE %s", quoter.Quote(model.TableName()), cols.Writeable().QuotedUpdateString(quoter), model.whereNamedID())
stmt := fmt.Sprintf("UPDATE %s AS %s SET %s WHERE %s", quoter.Quote(model.TableName()), model.alias(), cols.Writeable().QuotedUpdateString(quoter), model.whereNamedID())
log(logging.SQL, stmt, model.ID())
_, err := s.NamedExec(stmt, model.Value)
if err != nil {
Expand All @@ -109,7 +109,7 @@ func genericUpdate(s store, model *Model, cols columns.Columns, quoter quotable)
}

func genericDestroy(s store, model *Model, quoter quotable) error {
stmt := fmt.Sprintf("DELETE FROM %s WHERE %s", quoter.Quote(model.TableName()), model.whereID())
stmt := fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", quoter.Quote(model.TableName()), model.alias(), model.whereID())
_, err := genericExec(s, stmt, model.ID())
if err != nil {
return err
Expand Down
4 changes: 3 additions & 1 deletion dialect_mysql.go
Expand Up @@ -91,7 +91,9 @@ func (m *mysql) Update(s store, model *Model, cols columns.Columns) error {
}

func (m *mysql) Destroy(s store, model *Model) error {
return errors.Wrap(genericDestroy(s, model, m), "mysql destroy")
stmt := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", m.Quote(model.TableName()), model.IDField())
_, err := genericExec(s, stmt, model.ID())
return errors.Wrap(err, "mysql destroy")
}

func (m *mysql) SelectOne(s store, model *Model, query Query) error {
Expand Down
2 changes: 1 addition & 1 deletion dialect_postgresql.go
Expand Up @@ -92,7 +92,7 @@ func (p *postgresql) Update(s store, model *Model, cols columns.Columns) error {
}

func (p *postgresql) Destroy(s store, model *Model) error {
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s WHERE %s", p.Quote(model.TableName()), model.whereID()))
stmt := p.TranslateSQL(fmt.Sprintf("DELETE FROM %s AS %s WHERE %s", p.Quote(model.TableName()), model.alias(), model.whereID()))
_, err := genericExec(s, stmt, model.ID())
if err != nil {
return err
Expand Down
5 changes: 2 additions & 3 deletions docker-compose.yml
Expand Up @@ -24,10 +24,9 @@ services:
volumes:
- ./sqldumps:/docker-entrypoint-initdb.d
cockroach:
image: cockroachdb/cockroach:v2.1.0
image: cockroachdb/cockroach:v20.2.4
ports:
- "26257:26257"
- "8080:8080"
volumes:
- "./cockroach-data/roach1:/cockroach/cockroach-data"
command: start --insecure
command: start-single-node --insecure
14 changes: 6 additions & 8 deletions model.go
Expand Up @@ -223,21 +223,19 @@ func (m *Model) touchUpdatedAt() {
}

func (m *Model) whereID() string {
as := m.As
if as == "" {
as = strings.ReplaceAll(m.TableName(), ".", "_")
}

return fmt.Sprintf("%s.%s = ?", as, m.IDField())
return fmt.Sprintf("%s.%s = ?", m.alias(), m.IDField())
}

func (m *Model) whereNamedID() string {
func (m *Model) alias() string {
as := m.As
if as == "" {
as = strings.ReplaceAll(m.TableName(), ".", "_")
}
return as
}

return fmt.Sprintf("%s.%s = :%s", as, m.IDField(), m.IDField())
func (m *Model) whereNamedID() string {
return fmt.Sprintf("%s.%s = :%s", m.alias(), m.IDField(), m.IDField())
}

func (m *Model) isSlice() bool {
Expand Down
22 changes: 19 additions & 3 deletions model_context_test.go
Expand Up @@ -17,9 +17,24 @@ type ContextTable struct {
}

func (t ContextTable) TableName(ctx context.Context) string {
// This is singular on purpose! It will checck if the TableName is properly
// This is singular on purpose! It will check if the TableName is properly
// Respected in slices as well.
return "context_prefix_" + ctx.Value("prefix").(string) + "_table"
prefix := ctx.Value("prefix").(string)

// PostgreSQL and CockroachDB support schemas which work like a prefix. For those cases, we use
// the schema to ensure that name normalization does not cause query problems.
//
// Since this works only for those two databases, we use underscore for the rest.
//
// While this schema is hardcoded, it would have been too difficult to add this special
// case to the migrations.
switch PDB.Dialect.Name() {
case nameCockroach:
fallthrough
case namePostgreSQL:
prefix = prefix + "." + prefix
}
return "context_prefix_" + prefix + "_table"
}

func Test_ModelContext(t *testing.T) {
Expand All @@ -41,6 +56,7 @@ func Test_ModelContext(t *testing.T) {

expected := ContextTable{ID: prefix, Value: prefix}
c := PDB.WithContext(context.WithValue(context.Background(), "prefix", prefix))

r.NoError(c.Create(&expected))

var actual ContextTable
Expand Down Expand Up @@ -79,7 +95,7 @@ func Test_ModelContext(t *testing.T) {
err := c.Create(&ContextTable{ID: "unknown"})
r.Error(err)

if !strings.Contains(err.Error(), "context_prefix_unknown_table") { // All other databases
if !strings.Contains(err.Error(), "context_prefix_unknown") { // All other databases
t.Fatalf("Expected error to contain indicator that table does not exist but got: %s", err.Error())
}
})
Expand Down
10 changes: 2 additions & 8 deletions sql_builder.go
Expand Up @@ -125,10 +125,7 @@ func (sq *sqlBuilder) buildfromClauses() fromClauses {
fc := sq.Query.fromClauses
for _, m := range models {
tableName := m.TableName()
asName := m.As
if asName == "" {
asName = strings.Replace(tableName, ".", "_", -1)
}
asName := m.alias()
fc = append(fc, fromClause{
From: tableName,
As: asName,
Expand Down Expand Up @@ -216,10 +213,7 @@ var columnCacheMutex = sync.RWMutex{}

func (sq *sqlBuilder) buildColumns() columns.Columns {
tableName := sq.Model.TableName()
asName := sq.Model.As
if asName == "" {
asName = strings.Replace(tableName, ".", "_", -1)
}
asName := sq.Model.alias()
acl := len(sq.AddColumns)
if acl == 0 {
columnCacheMutex.RLock()
Expand Down
39 changes: 30 additions & 9 deletions testdata/migrations/20210104145901_context_tables.up.fizz
@@ -1,9 +1,30 @@
create_table("context_prefix_a_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}

create_table("context_prefix_b_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}
{{ if eq .Dialect "sqlite3" }}
create_table("context_prefix_a_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}

create_table("context_prefix_b_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}
{{ end }}

{{ if eq .Dialect "mysql" }}
create_table("context_prefix_a_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}

create_table("context_prefix_b_table") {
t.Column("id", "string", { primary: true })
t.Column("value", "string")
}
{{ end }}

{{ if eq .Dialect "postgres" }}
sql("CREATE SCHEMA IF NOT EXISTS \"context_prefix_a\";COMMIT TRANSACTION;BEGIN TRANSACTION;")
sql("CREATE SCHEMA IF NOT EXISTS \"context_prefix_b\";COMMIT TRANSACTION;BEGIN TRANSACTION;")
sql("CREATE TABLE \"context_prefix_a\".\"a_table\" (id character varying(255) NOT NULL, value character varying(255) NOT NULL, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL);COMMIT TRANSACTION;BEGIN TRANSACTION;")
sql("CREATE TABLE \"context_prefix_b\".\"b_table\" (id character varying(255) NOT NULL, value character varying(255) NOT NULL, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL);COMMIT TRANSACTION;BEGIN TRANSACTION;")
{{ end }}