3
0

Refactor and fix DAL tests

DAL tests are now moved to /tests/dal
This commit is contained in:
Denis Arh
2022-05-22 18:39:35 +02:00
parent 53b8c4951d
commit 040ad02e75
13 changed files with 540 additions and 727 deletions
+9
View File
@@ -32,6 +32,15 @@ func Connection(db *sqlx.DB, dialect drivers.Dialect, cc ...capabilities.Capabil
}
func (c *connection) model(m *dal.Model) *model {
if m.Resource == "" {
// if resource is empty, use ident
m.Resource = m.Ident
}
if m.Resource == "" {
panic("can not add model with empty resource")
}
c.mux.RLock()
if c.models[m.Resource] == nil {
c.mux.RUnlock()
+4
View File
@@ -54,6 +54,10 @@ func Model(m *dal.Model, c queryRunner, d drivers.Dialect) *model {
ms.queryParser = ql.Converter(
ql.SymHandler(func(node *ql.ASTNode) (exp.Expression, error) {
attr := ms.model.Attributes.FindByIdent(node.Symbol)
if attr == nil {
return nil, fmt.Errorf("unknown attribute %q used in query expression", node.Symbol)
}
if !attr.Filterable {
return nil, fmt.Errorf("attribute %q can not be used in query expression", attr.Ident)
}
@@ -1,111 +0,0 @@
package mysql
import (
"context"
"strings"
"testing"
"github.com/cortezaproject/corteza-server/pkg/dal/test"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func connect(req *require.Assertions) (db *sqlx.DB) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
)
cfg, err = NewConfig("mysql+debug://crust:crust@tcp(localhost:3306)/crust_store_test?collation=utf8mb4_general_ci&charset=utf8mb4")
req.NoError(err)
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
req.NoError(err)
return
}
func TestMySQL(t *testing.T) {
var (
req = require.New(t)
db = connect(req)
)
defer db.Close()
setupCodecTest(db, req)
setupRecordSearchTest(db, req)
test.All(t, dal.Connection(db, &dialect{}))
}
// remove when store support for table creation is added to CRS
//
// when support is added, test.TestRecordCodec
func setupCodecTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `DROP TABLE IF EXISTS crs_test_codec`)
req.NoError(err)
tblCreate := `
CREATE TABLE IF NOT EXISTS crs_test_codec (
"id" BIGINT UNSIGNED NOT NULL,
"created_at" DATETIME,
"updated_at" DATETIME,
"meta" JSON,
"pID" BIGINT UNSIGNED,
"pRef" BIGINT UNSIGNED,
"pTimestamp_TZT" DATETIME,
"pTimestamp_TZF" DATETIME,
"pTime" TIME,
"pDate" DATE,
"pNumber" NUMERIC,
"pText" TEXT,
"pBoolean_T" BOOLEAN,
"pBoolean_F" BOOLEAN,
"pEnum" TEXT,
"pGeometry" TEXT,
"pJSON" TEXT,
"pBlob" BLOB,
"pUUID" VARCHAR(36),
PRIMARY KEY("id")
)`
_, err = db.ExecContext(ctx, strings.ReplaceAll(tblCreate, "\"", "`"))
req.NoError(err)
}
// remove when store support for table creation is added to CRS
func setupRecordSearchTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `DROP TABLE IF EXISTS crs_test_search`)
req.NoError(err)
tblCreate := `
CREATE TABLE IF NOT EXISTS crs_test_search (
"id" INT NOT NULL,
"created_at" DATETIME,
"updated_at" DATETIME,
"meta" JSON,
"p_string" TEXT,
"p_number" NUMERIC,
"p_is_odd" BOOLEAN,
PRIMARY KEY("id")
)
`
_, err = db.ExecContext(ctx, strings.ReplaceAll(tblCreate, "\"", "`"))
req.NoError(err)
}
@@ -1,101 +0,0 @@
package postgres
import (
"context"
"testing"
"github.com/cortezaproject/corteza-server/pkg/dal/test"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func connect(req *require.Assertions) (db *sqlx.DB) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
)
cfg, err = NewConfig("postgres+debug://darh@localhost:5432/corteza_2022_3?sslmode=disable&")
req.NoError(err)
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
req.NoError(err)
return
}
func TestPostgres(t *testing.T) {
var (
req = require.New(t)
db = connect(req)
)
defer db.Close()
setupCodecTest(db, req)
setupRecordSearchTest(db, req)
test.All(t, dal.Connection(db, Dialect()))
}
// remove when store support for table creation is added to CRS
//
// when support is added, test.TestRecordCodec
func setupCodecTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `
CREATE TEMPORARY TABLE IF NOT EXISTS crs_test_codec (
"id" BIGINT NOT NULL,
"created_at" TIMESTAMP NOT NULL,
"updated_at" TIMESTAMP,
"meta" JSON,
"pID" BIGINT,
"pRef" BIGINT,
"pTimestamp_TZT" TIMESTAMPTZ,
"pTimestamp_TZF" TIMESTAMP,
"pTime" TIME,
"pDate" DATE,
"pNumber" NUMERIC,
"pText" TEXT,
"pBoolean_T" BOOLEAN,
"pBoolean_F" BOOLEAN,
"pEnum" TEXT,
"pGeometry" TEXT,
"pJSON" TEXT,
"pBlob" BYTEA,
"pUUID" UUID,
PRIMARY KEY(id)
)`)
req.NoError(err)
}
// remove when store support for table creation is added to CRS
func setupRecordSearchTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `
CREATE TEMPORARY TABLE IF NOT EXISTS crs_test_search (
"id" BIGINT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL,
"updated_at" TIMESTAMPTZ,
"meta" JSON,
"p_string" TEXT,
"p_number" NUMERIC,
"p_is_odd" BOOLEAN,
PRIMARY KEY(id )
)
`)
req.NoError(err)
}
@@ -1,105 +0,0 @@
package sqlite
import (
"context"
"testing"
"github.com/cortezaproject/corteza-server/pkg/dal/test"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
)
func connect(req *require.Assertions) (db *sqlx.DB) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
)
cfg, err = NewConfig("sqlite3+debug://file::memory:?cache=shared&mode=memory")
//cfg, err = NewConfig("sqlite3://file:/tmp/test.db")
req.NoError(err)
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
req.NoError(err)
return
}
func TestSQLite(t *testing.T) {
var (
req = require.New(t)
db = connect(req)
)
defer db.Close()
setupCodecTest(db, req)
setupRecordSearchTest(db, req)
test.All(t, dal.Connection(db, Dialect()))
}
// remove when store support for table creation is added to CRS
//
// when support is added, test.TestRecordCodec
func setupCodecTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `DROP TABLE IF EXISTS crs_test_codec`)
req.NoError(err)
_, err = db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS crs_test_codec (
id UNSIGNED BIG INT NOT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,
meta JSON,
pID UNSIGNED BIG INT,
pRef UNSIGNED BIG INT,
pTimestamp_TZT TIMESTAMP,
pTimestamp_TZF TIMESTAMP,
pTime TIME,
pDate DATE,
pNumber NUMERIC,
pText TEXT,
pBoolean_T BOOLEAN,
pBoolean_F BOOLEAN,
pEnum TEXT,
pGeometry TEXT,
pJSON TEXT,
pBlob BLOB,
pUUID UUID,
PRIMARY KEY(id)
)`)
req.NoError(err)
}
// remove when store support for table creation is added to CRS
func setupRecordSearchTest(db sqlx.ExecerContext, req *require.Assertions) {
var (
err error
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
)
_, err = db.ExecContext(ctx, `DROP TABLE IF EXISTS crs_test_search`)
req.NoError(err)
_, err = db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS crs_test_search (
id INT NOT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,
meta JSON,
p_string TEXT,
p_number NUMERIC,
p_is_odd BOOLEAN,
PRIMARY KEY(id )
)
`)
req.NoError(err)
}
+2 -206
View File
@@ -1,31 +1,19 @@
package test
package dal
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/stretchr/testify/require"
)
func All(t *testing.T, d dal.Connection) {
t.Run("RecordCodec", func(t *testing.T) { RecordCodec(t, d) })
t.Run("RecordSearch", func(t *testing.T) { RecordSearch(t, d) })
}
func RecordCodec(t *testing.T, d dal.Connection) {
func RecordCodec(t *testing.T, ctx context.Context, d dal.Connection) {
var (
req = require.New(t)
// enable query logging when +debug is used on DSN schema
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
m = &dal.Model{
Ident: "crs_test_codec",
Attributes: dal.AttributeSet{
@@ -133,195 +121,3 @@ func RecordCodec(t *testing.T, d dal.Connection) {
req.Equal(vIn, vOut, "values for attribute %q are not equal", attr.Ident)
}
}
func RecordSearch(t *testing.T, d dal.Connection) {
const (
totalRecords = 10
)
var (
req = require.New(t)
// enable query logging when +debug is used on DSN schema
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
m = &dal.Model{
Ident: "crs_test_search",
Attributes: dal.AttributeSet{
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
&dal.Attribute{Ident: "createdAt", Type: &dal.TypeTimestamp{}, Store: &dal.CodecAlias{Ident: "created_at"}},
&dal.Attribute{Ident: "updatedAt", Type: &dal.TypeTimestamp{}, Store: &dal.CodecAlias{Ident: "updated_at"}},
&dal.Attribute{Ident: "v_string", Type: &dal.TypeText{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "v_number", Type: &dal.TypeNumber{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "v_is_odd", Type: &dal.TypeBoolean{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "p_string", Type: &dal.TypeText{}, Store: &dal.CodecPlain{}},
&dal.Attribute{Ident: "p_number", Type: &dal.TypeNumber{}, Store: &dal.CodecPlain{}},
&dal.Attribute{Ident: "p_is_odd", Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
},
}
)
for ID := uint64(1); ID <= totalRecords; ID++ {
r := &types.Record{ID: ID, CreatedAt: time.Now()}
i := int(ID)
r.Values = r.Values.Set(&types.RecordValue{Name: "v_string", Value: "tens_" + strconv.Itoa(i%10)})
r.Values = r.Values.Set(&types.RecordValue{Name: "v_number", Value: strconv.Itoa(i)})
r.Values = r.Values.Set(&types.RecordValue{Name: "v_is_odd", Value: strconv.FormatBool(i%2 == 1)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_string", Value: "tens_" + strconv.Itoa(i%10)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_number", Value: strconv.Itoa(i)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_is_odd", Value: strconv.FormatBool(i%2 == 1)})
req.NoError(d.Create(ctx, m, r))
}
cases := []struct {
f types.RecordFilter
total int
}{
{
total: totalRecords,
},
{
f: types.RecordFilter{Query: "v_string == p_string"},
total: totalRecords,
},
{
f: types.RecordFilter{Query: "v_number == p_number"},
total: totalRecords,
},
{
f: types.RecordFilter{Query: "p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "true = p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "p_is_odd = true"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "!p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "p_number = 1"},
total: 1,
},
}
for _, c := range cases {
t.Run(c.f.Query, func(t *testing.T) {
var (
req = require.New(t)
)
i, err := d.Search(ctx, m, c.f.ToFilter())
req.NoError(err)
rr, err := drain(ctx, i)
req.NoError(err)
req.Len(rr, c.total)
})
}
t.Run("paging", func(t *testing.T) {
var (
req = require.New(t)
ids string
fwd, bck *filter.PagingCursor
search = func(where, orderBy string, lim uint, cur *filter.PagingCursor) (ids string, fwd, bck *filter.PagingCursor) {
f := types.RecordFilter{Query: where}
f.PageCursor = cur
f.Limit = lim
req.NoError(f.Sort.Set(orderBy))
i, err := d.Search(ctx, m, f.ToFilter())
req.NoError(err)
req.NoError(i.Err())
if !i.Next(ctx) {
req.NoError(i.Err())
return
}
r := new(types.Record)
req.NoError(i.Scan(r))
bck, err = i.BackCursor(r)
req.NoError(err)
t.Logf("bck-cursor (from the 1st fetched record): %v", bck)
rr, err := drain(ctx, i)
req.NoError(err)
if len(rr) > 0 {
fwd, err = i.ForwardCursor(rr[len(rr)-1])
req.NoError(err)
t.Logf("fwd-cursor (from the lst fetched record): %v", fwd)
}
ids = fmt.Sprintf("%d", r.ID)
for _, r = range rr {
ids += fmt.Sprintf(",%d", r.ID)
}
return
}
)
ids, fwd, _ = search("", "", 3, nil)
req.Equal("1,2,3", ids)
ids, fwd, _ = search("", "", 3, fwd)
req.Equal("4,5,6", ids)
ids, fwd, _ = search("", "", 3, fwd)
req.Equal("7,8,9", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("10", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("7,8,9", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("4,5,6", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("1,2,3", ids)
ids, fwd, _ = search("", "p_is_odd", 3, nil)
req.Equal("2,4,6", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("8,10,1", ids)
ids, _, _ = search("", "", 3, bck)
req.Equal("2,4,6", ids)
ids, fwd, _ = search("", "v_is_odd", 3, nil)
req.Equal("2,4,6", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("8,10,1", ids)
ids, _, _ = search("", "", 3, bck)
req.Equal("2,4,6", ids)
_, _ = fwd, bck // avoiding unused var. error
})
}
func drain(ctx context.Context, i dal.Iterator) (rr []*types.Record, err error) {
var r *types.Record
rr = make([]*types.Record, 0, 100)
for i.Next(ctx) {
if i.Err() != nil {
return nil, i.Err()
}
r = new(types.Record)
if err = i.Scan(r); err != nil {
return
}
rr = append(rr, r)
}
return rr, i.Err()
}
-34
View File
@@ -1,34 +0,0 @@
package dal
import "github.com/cortezaproject/corteza-server/pkg/dal/capabilities"
type (
connection struct {
id uint64
dsn string
capabilities capabilities.Set
}
)
// Connection is a utility to define generic store connections
func Connection(id uint64, dsn string, cc ...capabilities.Capability) connection {
return connection{
id: id,
dsn: dsn,
capabilities: cc,
}
}
// Receivers to conform to interface
func (s connection) ComposeRecordStoreID() uint64 {
return s.id
}
func (s connection) StoreDSN() string {
return s.dsn
}
func (s connection) Capabilities() capabilities.Set {
return s.capabilities
}
+69 -170
View File
@@ -2,184 +2,83 @@ package dal
import (
"context"
"testing"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/dal/capabilities"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/stretchr/testify/require"
_ "github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers/mysql"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/tests/dal/setup/mysql"
"github.com/cortezaproject/corteza-server/tests/dal/setup/postgres"
"github.com/cortezaproject/corteza-server/tests/dal/setup/sqlite"
"github.com/cortezaproject/corteza-server/tests/helpers"
_ "github.com/joho/godotenv/autoload"
"os"
"strings"
"testing"
)
/*
See here for my table definitions
// Tests DAL functionalities for all supported databases
//
// Tests for drivers are executed if DSN env variable (DAL_TEST_DSN_<driver>) is present.
// Example: DAL_TEST_DSN_MYSQL, DAL_TEST_DSN_POSTGRES
//
// Multiple space delimited DSNs are supported.
//
// Tests scans current and two parent folders for presence of .env file
// and loads the first one found.
//
//
func TestDAL(t *testing.T) {
helpers.RecursiveDotEnvLoad()
CREATE TABLE `the_cake` (
`id` bigint unsigned NOT NULL,
`name` VARCHAR(45),
`want` BOOLEAN,
`ownedBy` bigint unsigned NOT NULL,
`createdAt` datetime NOT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
`createdBy` bigint unsigned NOT NULL,
`updatedBy` bigint unsigned NOT NULL DEFAULT '0',
`deletedBy` bigint unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `compose_record_owner` (`ownedBy`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3;
var (
// enrich context with debug logger
//
// this will enable us to log driver commands
// when +debug is used on DSN schema
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
CREATE TABLE `the_cookie` (
`id` bigint unsigned NOT NULL,
`name` VARCHAR(45),
`is_good` BOOLEAN,
`ownedBy` bigint unsigned NOT NULL,
`createdAt` datetime NOT NULL,
`updatedAt` datetime DEFAULT NULL,
`deletedAt` datetime DEFAULT NULL,
`createdBy` bigint unsigned NOT NULL,
`updatedBy` bigint unsigned NOT NULL DEFAULT '0',
`deletedBy` bigint unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `compose_record_owner` (`ownedBy`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb3;
*/
conn dal.Connection
err error
func TestHello(t *testing.T) {
ctx := context.Background()
c, err := dal.Service(
ctx,
nil,
false,
// Primary...
Connection(0, "mysql://envoy:envoy@tcp(localhost:3306)/crs?collation=utf8mb4_general_ci", capabilities.FullCapabilities()...),
// Others...
Connection(1, "mysql://envoy:envoy@tcp(localhost:3306)/crs?collation=utf8mb4_general_ci", capabilities.FullCapabilities()...),
drivers = []struct {
name string
dsn string
connect func(string) (dal.Connection, error)
}{
{
name: "sqlite",
dsn: "sqlite3+debug://file::memory:?cache=shared&mode=memory",
connect: sqlite.Setup,
},
{
name: "mysql",
dsn: os.Getenv("DAL_TEST_DSN_MYSQL"),
connect: mysql.Setup,
},
{
name: "postgres",
dsn: os.Getenv("DAL_TEST_DSN_POSTGRES"),
connect: postgres.Setup,
},
}
)
require.NoError(t, err)
_ = c
// ---
for _, driver := range drivers {
t.Run(driver.name, func(t *testing.T) {
if driver.dsn == "" {
t.Skip("DSN for DAL test not set")
}
cookieModule := &types.Module{
ID: 10001,
Handle: "cookie",
Store: types.DalDef{
ComposeRecordStoreID: 0,
Capabilities: capabilities.FullCapabilities(),
Partitioned: true,
PartitionFormat: "the_{{module}}",
},
Fields: types.ModuleFieldSet{&types.ModuleField{
ModuleID: 10001,
ID: 20001,
Name: "name",
Encoding: types.EncodingStrategy{
EncodingStrategyAlias: &types.EncodingStrategyAlias{
Ident: "name",
},
},
}, &types.ModuleField{
ModuleID: 10001,
ID: 20002,
Name: "is_good",
Kind: "Bool",
Encoding: types.EncodingStrategy{
EncodingStrategyAlias: &types.EncodingStrategyAlias{
Ident: "is_good",
},
},
}},
for _, dsn := range strings.Split(driver.dsn, " ") {
t.Run("", func(t *testing.T) {
t.Log("Connecting to ", dsn)
if conn, err = driver.connect(dsn); err != nil {
t.Fatal(err)
}
t.Run("RecordCodec", func(t *testing.T) { RecordCodec(t, ctx, conn) })
t.Run("RecordSearch", func(t *testing.T) { RecordSearch(t, ctx, conn) })
})
}
})
}
cakeModule := &types.Module{
ID: 10002,
Handle: "cake",
Store: types.DalDef{
ComposeRecordStoreID: 1,
Capabilities: capabilities.FullCapabilities(),
Partitioned: true,
PartitionFormat: "the_{{module}}",
},
Fields: types.ModuleFieldSet{&types.ModuleField{
ModuleID: 10002,
ID: 20003,
Name: "name",
Encoding: types.EncodingStrategy{
EncodingStrategyAlias: &types.EncodingStrategyAlias{
// this doesn't work
Ident: "name",
},
},
}, &types.ModuleField{
ModuleID: 10002,
ID: 20004,
Name: "want",
Kind: "Bool",
Encoding: types.EncodingStrategy{
EncodingStrategyAlias: &types.EncodingStrategyAlias{
Ident: "want",
},
},
}},
}
err = c.ReloadModules(ctx, cookieModule, cakeModule)
require.NoError(t, err)
// ---
a := time.Now()
cookieRecord := &types.Record{
ID: id.Next(),
ModuleID: 10001,
Values: types.RecordValueSet{{
Name: "name",
Value: "SOME COOKIE HERE",
}, {
Name: "is_good",
Value: "1",
}},
CreatedAt: time.Now(),
UpdatedAt: &a,
DeletedAt: &a,
OwnedBy: 20003,
CreatedBy: 20003,
UpdatedBy: 20003,
DeletedBy: 20003,
}
cakeRecord := &types.Record{
ID: id.Next(),
ModuleID: 10002,
Values: types.RecordValueSet{{
Name: "name",
Value: "DOME CAKE HERE",
}, {
Name: "want",
Value: "1",
}},
CreatedAt: time.Now(),
UpdatedAt: &a,
DeletedAt: &a,
OwnedBy: 20003,
CreatedBy: 20003,
UpdatedBy: 20003,
DeletedBy: 20003,
}
err = c.ComposeRecordCreate(ctx, cookieModule, cookieRecord)
require.NoError(t, err)
err = c.ComposeRecordCreate(ctx, cakeModule, cakeRecord)
require.NoError(t, err)
require.FailNow(t, "the test dies a failNow so I can see logs!!!")
}
+26
View File
@@ -0,0 +1,26 @@
package dal
import (
"context"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/dal"
)
func drain(ctx context.Context, i dal.Iterator) (rr []*types.Record, err error) {
var r *types.Record
rr = make([]*types.Record, 0, 100)
for i.Next(ctx) {
if i.Err() != nil {
return nil, i.Err()
}
r = new(types.Record)
if err = i.Scan(r); err != nil {
return
}
rr = append(rr, r)
}
return rr, i.Err()
}
+184
View File
@@ -0,0 +1,184 @@
package dal
import (
"context"
"fmt"
"strconv"
"testing"
"time"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/stretchr/testify/require"
)
func RecordSearch(t *testing.T, ctx context.Context, d dal.Connection) {
const (
totalRecords = 10
)
var (
req = require.New(t)
m = &dal.Model{
Ident: "crs_test_search",
Attributes: dal.AttributeSet{
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
&dal.Attribute{Ident: "createdAt", Type: &dal.TypeTimestamp{}, Store: &dal.CodecAlias{Ident: "created_at"}},
&dal.Attribute{Ident: "updatedAt", Type: &dal.TypeTimestamp{}, Store: &dal.CodecAlias{Ident: "updated_at"}},
&dal.Attribute{Ident: "v_string", Filterable: true, Type: &dal.TypeText{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "v_number", Filterable: true, Type: &dal.TypeNumber{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "v_is_odd", Filterable: true, Type: &dal.TypeBoolean{}, Store: &dal.CodecRecordValueSetJSON{Ident: "meta"}},
&dal.Attribute{Ident: "p_string", Filterable: true, Type: &dal.TypeText{}, Store: &dal.CodecPlain{}},
&dal.Attribute{Ident: "p_number", Filterable: true, Type: &dal.TypeNumber{}, Store: &dal.CodecPlain{}},
&dal.Attribute{Ident: "p_is_odd", Filterable: true, Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
},
}
)
for ID := uint64(1); ID <= totalRecords; ID++ {
r := &types.Record{ID: ID, CreatedAt: time.Now()}
i := int(ID)
r.Values = r.Values.Set(&types.RecordValue{Name: "v_string", Value: "tens_" + strconv.Itoa(i%10)})
r.Values = r.Values.Set(&types.RecordValue{Name: "v_number", Value: strconv.Itoa(i)})
r.Values = r.Values.Set(&types.RecordValue{Name: "v_is_odd", Value: strconv.FormatBool(i%2 == 1)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_string", Value: "tens_" + strconv.Itoa(i%10)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_number", Value: strconv.Itoa(i)})
r.Values = r.Values.Set(&types.RecordValue{Name: "p_is_odd", Value: strconv.FormatBool(i%2 == 1)})
req.NoError(d.Create(ctx, m, r))
}
cases := []struct {
f types.RecordFilter
total int
}{
{
total: totalRecords,
},
{
f: types.RecordFilter{Query: "v_string == p_string"},
total: totalRecords,
},
{
f: types.RecordFilter{Query: "v_number == p_number"},
total: totalRecords,
},
{
f: types.RecordFilter{Query: "p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "true = p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "p_is_odd = true"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "!p_is_odd"},
total: totalRecords / 2,
},
{
f: types.RecordFilter{Query: "p_number = 1"},
total: 1,
},
}
for _, c := range cases {
t.Run(c.f.Query, func(t *testing.T) {
var (
req = require.New(t)
)
i, err := d.Search(ctx, m, c.f.ToFilter())
req.NoError(err)
rr, err := drain(ctx, i)
req.NoError(err)
req.Len(rr, c.total)
})
}
t.Run("paging", func(t *testing.T) {
var (
req = require.New(t)
ids string
fwd, bck *filter.PagingCursor
search = func(where, orderBy string, lim uint, cur *filter.PagingCursor) (ids string, fwd, bck *filter.PagingCursor) {
f := types.RecordFilter{Query: where}
f.PageCursor = cur
f.Limit = lim
req.NoError(f.Sort.Set(orderBy))
i, err := d.Search(ctx, m, f.ToFilter())
req.NoError(err)
req.NoError(i.Err())
if !i.Next(ctx) {
req.NoError(i.Err())
return
}
r := new(types.Record)
req.NoError(i.Scan(r))
bck, err = i.BackCursor(r)
req.NoError(err)
t.Logf("bck-cursor (from the 1st fetched record): %v", bck)
rr, err := drain(ctx, i)
req.NoError(err)
if len(rr) > 0 {
fwd, err = i.ForwardCursor(rr[len(rr)-1])
req.NoError(err)
t.Logf("fwd-cursor (from the lst fetched record): %v", fwd)
}
ids = fmt.Sprintf("%d", r.ID)
for _, r = range rr {
ids += fmt.Sprintf(",%d", r.ID)
}
return
}
)
ids, fwd, _ = search("", "", 3, nil)
req.Equal("1,2,3", ids)
ids, fwd, _ = search("", "", 3, fwd)
req.Equal("4,5,6", ids)
ids, fwd, _ = search("", "", 3, fwd)
req.Equal("7,8,9", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("10", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("7,8,9", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("4,5,6", ids)
ids, _, bck = search("", "", 3, bck)
req.Equal("1,2,3", ids)
ids, fwd, _ = search("", "p_is_odd", 3, nil)
req.Equal("2,4,6", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("8,10,1", ids)
ids, _, _ = search("", "", 3, bck)
req.Equal("2,4,6", ids)
ids, fwd, _ = search("", "v_is_odd", 3, nil)
req.Equal("2,4,6", ids)
ids, _, bck = search("", "", 3, fwd)
req.Equal("8,10,1", ids)
ids, _, _ = search("", "", 3, bck)
req.Equal("2,4,6", ids)
_, _ = fwd, bck // avoiding unused var. error
})
}
+84
View File
@@ -0,0 +1,84 @@
package mysql
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
rdbmsDAL "github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers/mysql"
"github.com/jmoiron/sqlx"
"strings"
)
func Setup(dsn string) (_ dal.Connection, err error) {
var (
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
db *sqlx.DB
)
cfg, err = mysql.NewConfig(dsn)
if err != nil {
return
}
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
if err != nil {
return
}
if err = tables(ctx, db); err != nil {
return
}
return rdbmsDAL.Connection(db, mysql.Dialect()), nil
}
// remove when store support for table creation is added to CRS
//
// When support for creating DDL commands (creating tables) from DAL models and attributes
// is added, this can be removed!
func tables(ctx context.Context, db sqlx.ExecerContext) (err error) {
return ddl.Exec(ctx, db,
`DROP TABLE IF EXISTS crs_test_codec`,
strings.ReplaceAll(
`CREATE TABLE IF NOT EXISTS crs_test_codec (
"id" BIGINT UNSIGNED,
"created_at" DATETIME,
"updated_at" DATETIME,
"meta" JSON,
"pID" BIGINT UNSIGNED,
"pRef" BIGINT UNSIGNED,
"pTimestamp_TZT" DATETIME,
"pTimestamp_TZF" DATETIME,
"pTime" TIME,
"pDate" DATE,
"pNumber" NUMERIC,
"pText" TEXT,
"pBoolean_T" BOOLEAN,
"pBoolean_F" BOOLEAN,
"pEnum" TEXT,
"pGeometry" TEXT,
"pJSON" TEXT,
"pBlob" BLOB,
"pUUID" VARCHAR(36),
PRIMARY KEY("id")
)`, "\"", "`"),
`DROP TABLE IF EXISTS crs_test_search`,
strings.ReplaceAll(
`CREATE TABLE IF NOT EXISTS crs_test_search (
"id" INT NOT NULL,
"created_at" DATETIME,
"updated_at" DATETIME,
"meta" JSON,
"p_string" TEXT,
"p_number" NUMERIC,
"p_is_odd" BOOLEAN,
PRIMARY KEY("id")
)`, "\"", "`"),
)
}
+80
View File
@@ -0,0 +1,80 @@
package postgres
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
rdbmsDAL "github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers/postgres"
"github.com/jmoiron/sqlx"
)
func Setup(dsn string) (_ dal.Connection, err error) {
var (
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
db *sqlx.DB
)
cfg, err = postgres.NewConfig(dsn)
if err != nil {
return
}
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
if err != nil {
return
}
if err = tables(ctx, db); err != nil {
return
}
return rdbmsDAL.Connection(db, postgres.Dialect()), nil
}
// remove when store support for table creation is added to CRS
//
// When support for creating DDL commands (creating tables) from DAL models and attributes
// is added, this can be removed!
func tables(ctx context.Context, db sqlx.ExecerContext) (err error) {
return ddl.Exec(ctx, db,
`CREATE TEMPORARY TABLE IF NOT EXISTS crs_test_codec (
"id" BIGINT NOT NULL,
"created_at" TIMESTAMP NOT NULL,
"updated_at" TIMESTAMP,
"meta" JSON,
"pID" BIGINT,
"pRef" BIGINT,
"pTimestamp_TZT" TIMESTAMPTZ,
"pTimestamp_TZF" TIMESTAMP,
"pTime" TIME,
"pDate" DATE,
"pNumber" NUMERIC,
"pText" TEXT,
"pBoolean_T" BOOLEAN,
"pBoolean_F" BOOLEAN,
"pEnum" TEXT,
"pGeometry" TEXT,
"pJSON" TEXT,
"pBlob" BYTEA,
"pUUID" UUID,
PRIMARY KEY(id)
)`,
`CREATE TEMPORARY TABLE IF NOT EXISTS crs_test_search (
"id" BIGINT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL,
"updated_at" TIMESTAMPTZ,
"meta" JSON,
"p_string" TEXT,
"p_number" NUMERIC,
"p_is_odd" BOOLEAN,
PRIMARY KEY(id )
)`,
)
}
+82
View File
@@ -0,0 +1,82 @@
package sqlite
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/dal"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
rdbmsDAL "github.com/cortezaproject/corteza-server/store/adapters/rdbms/dal"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/ddl"
"github.com/cortezaproject/corteza-server/store/adapters/rdbms/drivers/sqlite"
"github.com/jmoiron/sqlx"
)
func Setup(dsn string) (_ dal.Connection, err error) {
var (
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
cfg *rdbms.ConnConfig
db *sqlx.DB
)
cfg, err = sqlite.NewConfig(dsn)
if err != nil {
return
}
db, err = rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
if err != nil {
return
}
if err = tables(ctx, db); err != nil {
return
}
return rdbmsDAL.Connection(db, sqlite.Dialect()), nil
}
// remove when store support for table creation is added to CRS
//
// When support for creating DDL commands (creating tables) from DAL models and attributes
// is added, this can be removed!
func tables(ctx context.Context, db sqlx.ExecerContext) (err error) {
return ddl.Exec(ctx, db,
`DROP TABLE IF EXISTS crs_test_codec`,
`CREATE TABLE IF NOT EXISTS crs_test_codec (
id UNSIGNED BIG INT NOT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,
meta JSON,
pID UNSIGNED BIG INT,
pRef UNSIGNED BIG INT,
pTimestamp_TZT TIMESTAMP,
pTimestamp_TZF TIMESTAMP,
pTime TIME,
pDate DATE,
pNumber NUMERIC,
pText TEXT,
pBoolean_T BOOLEAN,
pBoolean_F BOOLEAN,
pEnum TEXT,
pGeometry TEXT,
pJSON TEXT,
pBlob BLOB,
pUUID UUID,
PRIMARY KEY(id)
)`,
`DROP TABLE IF EXISTS crs_test_search`,
`CREATE TABLE IF NOT EXISTS crs_test_search (
id INT NOT NULL,
created_at TIMESTAMP,
updated_at TIMESTAMP,
meta JSON,
p_string TEXT,
p_number NUMERIC,
p_is_odd BOOLEAN,
PRIMARY KEY(id )
)`,
)
}