diff --git a/c_string_cache.go b/c_string_cache.go index 1a487fb..7ed0617 100644 --- a/c_string_cache.go +++ b/c_string_cache.go @@ -7,7 +7,7 @@ import ( ) var cache = map[string]*C.char{} -var mLock = sync.Mutex{} +var mLock sync.Mutex func getCStringFromCache(str string) *C.char { cStr, ok := cache[str] diff --git a/cursor.go b/cursor.go index 8cc66f7..c4ebefb 100644 --- a/cursor.go +++ b/cursor.go @@ -28,21 +28,11 @@ const ( CursorOrder = "order" ) -// Cursor iterates over records in a database. -type Cursor interface { - // Next fetches the next row for the cursor - // Returns next row if it exists else it will return nil - Next() *Document - // Close closes cursor - // Cursor won't be accessible after this - Close() error -} - // ErrCursorClosed will be returned in case of closed cursor usage -var ErrCursorClosed = errors.New("usage of closed cursor") +var ErrCursorClosed = errors.New("usage of closed Cursor") // Cursor iterates over key-values in a database. -type cursor struct { +type Cursor struct { ptr unsafe.Pointer doc *Document closed bool @@ -50,7 +40,8 @@ type cursor struct { // Close closes the cursor. If a cursor is not closed, future operations // on the database can hang indefinitely. -func (cur *cursor) Close() error { +// Cursor won't be accessible after this +func (cur *Cursor) Close() error { if cur.closed { return ErrCursorClosed } @@ -64,7 +55,7 @@ func (cur *cursor) Close() error { // Next fetches the next row for the cursor // Returns next row if it exists else it will return nil -func (cur *cursor) Next() *Document { +func (cur *Cursor) Next() *Document { if cur.closed { return nil } diff --git a/cursor_test.go b/cursor_test.go index ee7236a..83c7878 100644 --- a/cursor_test.go +++ b/cursor_test.go @@ -11,43 +11,50 @@ import ( ) func TestCursor(t *testing.T) { - dbDir, err := ioutil.TempDir("", "sophia") - require.Nil(t, err, "failed to create temp dir for database") - defer func() { - require.Nil(t, os.RemoveAll(dbDir)) - }() + const ( + keyPath = "key" + valuePath = "value" + recordsCount = 100 + valueTemplate = "value%011v" + ) + + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() - require.Nil(t, err, "failed to create new environment") - defer func() { - require.Nil(t, env.Close()) - }() + require.Nil(t, err) + require.NotNil(t, env) - require.True(t, env.SetString("sophia.path", dbDir)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} require.Nil(t, schema.AddKey("key", FieldTypeUInt64)) require.Nil(t, schema.AddValue("value", FieldTypeString)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, }) - require.Nil(t, err, "failed to create database") - require.Nil(t, env.Open(), "failed to open environment") + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() - for i := 0; i < RecordsCount; i++ { + for i := 0; i < recordsCount; i++ { doc := db.Document() - require.True(t, doc.SetInt("key", int64(i))) - require.True(t, doc.SetString("value", fmt.Sprintf(ValueTemplate, i))) + require.True(t, doc.SetInt(keyPath, int64(i))) + require.True(t, doc.SetString(valuePath, fmt.Sprintf(valueTemplate, i))) require.Nil(t, db.Set(doc)) doc.Free() } - t.Run("All records", func(t *testing.T) { testCursor(t, db, 0) }) - t.Run("Half records", func(t *testing.T) { testCursor(t, db, RecordsCount/2) }) - t.Run("Quater records", func(t *testing.T) { testCursor(t, db, RecordsCount/4) }) + t.Run("All records", func(t *testing.T) { testCursor(t, db, 0, recordsCount, valueTemplate) }) + t.Run("Half records", func(t *testing.T) { testCursor(t, db, recordsCount/2, recordsCount, valueTemplate) }) + t.Run("Quater records", func(t *testing.T) { testCursor(t, db, recordsCount/4, recordsCount, valueTemplate) }) t.Run("Use closed cursor error", func(t *testing.T) { testCursorError(t, db) }) - t.Run("Reverse iterator", func(t *testing.T) { testReverseCursor(t, db) }) + t.Run("Reverse iterator", func(t *testing.T) { testReverseCursor(t, db, recordsCount, valueTemplate) }) } func testCursorError(t *testing.T, db *Database) { @@ -65,7 +72,7 @@ func testCursorError(t *testing.T, db *Database) { require.Nil(t, cursor.Next()) } -func testCursor(t *testing.T, db *Database, start int64) { +func testCursor(t *testing.T, db *Database, start, count int64, valueTemplate string) { id := start doc := db.Document() require.NotNil(t, doc) @@ -86,14 +93,14 @@ func testCursor(t *testing.T, db *Database, start int64) { ) for d := cursor.Next(); d != nil; d = cursor.Next() { require.Equal(t, id, d.GetInt("key")) - require.Equal(t, fmt.Sprintf(ValueTemplate, id), d.GetString("value", &size)) + require.Equal(t, fmt.Sprintf(valueTemplate, id), d.GetString("value", &size)) counter++ id++ } - require.Equal(t, RecordsCount-start, counter) + require.Equal(t, count-start, counter) } -func testReverseCursor(t *testing.T, db *Database) { +func testReverseCursor(t *testing.T, db *Database, count int64, valueTemplate string) { doc := db.Document() require.NotNil(t, doc) @@ -108,65 +115,71 @@ func testReverseCursor(t *testing.T, db *Database) { var ( size int - id int64 = RecordsCount - 1 + id int64 = count - 1 ) for d := cursor.Next(); d != nil; d = cursor.Next() { require.Equal(t, id, d.GetInt("key")) - require.Equal(t, fmt.Sprintf(ValueTemplate, id), d.GetString("value", &size)) + require.Equal(t, fmt.Sprintf(valueTemplate, id), d.GetString("value", &size)) id-- } } func TestCursorPrefix(t *testing.T) { - dbDir, err := ioutil.TempDir("", "sophia") - require.Nil(t, err, "failed to create temp dir for database") - defer func() { - require.Nil(t, os.RemoveAll(dbDir)) - }() + const ( + keyPath = "key" + valuePath = "value" + recordsCount = 100 + valueTemplate = "value%011v" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() - require.Nil(t, err, "failed to create new environment") - defer func() { - require.Nil(t, env.Close()) - }() + require.Nil(t, err) + require.NotNil(t, env) - require.True(t, env.SetString("sophia.path", dbDir)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeString)) - require.Nil(t, schema.AddValue("value", FieldTypeString)) + require.Nil(t, schema.AddKey(keyPath, FieldTypeString)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeString)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, }) - require.Nil(t, err, "failed to create database") - require.Nil(t, env.Open(), "failed to open environment") + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() const base = 36 - for i := int64(0); i < RecordsCount; i++ { + for i := int64(0); i < recordsCount; i++ { doc := db.Document() - require.True(t, doc.SetString("key", strconv.FormatInt(i, base))) - require.True(t, doc.SetString("value", fmt.Sprintf(ValueTemplate, i))) + require.True(t, doc.SetString(keyPath, strconv.FormatInt(i, base))) + require.True(t, doc.SetString(valuePath, fmt.Sprintf(valueTemplate, i))) require.Nil(t, db.Set(doc)) doc.Free() } // Calculate prefix for inserted rows - c := RecordsCount + c := recordsCount maxDigit := 1 for c > base { c /= base maxDigit *= base } - expectedCount := RecordsCount + expectedCount := recordsCount for maxDigit != 1 { c := expectedCount / maxDigit expectedCount -= c * maxDigit maxDigit /= base } - prefix := RecordsCount - expectedCount + prefix := recordsCount - expectedCount prefixStr := strconv.FormatInt(int64(prefix), base) prefixStr = prefixStr[:len(prefixStr)-1] @@ -190,14 +203,15 @@ func TestCursorPrefix(t *testing.T) { // get row that match prefix d := cursor.Next() - require.Equal(t, prefixStr, d.GetString("key", &size)) - require.Equal(t, fmt.Sprintf(ValueTemplate, prefix/base), d.GetString("value", &size)) + require.NotNil(t, d) + require.Equal(t, prefixStr, d.GetString(keyPath, &size)) + require.Equal(t, fmt.Sprintf(valueTemplate, prefix/base), d.GetString(valuePath, &size)) // get rows that have additional symbol at the end for d := cursor.Next(); d != nil; d = cursor.Next() { id := prefix + count - require.Equal(t, strconv.FormatInt(int64(id), base), d.GetString("key", &size)) - require.Equal(t, fmt.Sprintf(ValueTemplate, id), d.GetString("value", &size)) + require.Equal(t, strconv.FormatInt(int64(id), base), d.GetString(keyPath, &size)) + require.Equal(t, fmt.Sprintf(valueTemplate, id), d.GetString(valuePath, &size)) count++ } diff --git a/database.go b/database.go index ce9417b..c986c64 100644 --- a/database.go +++ b/database.go @@ -6,17 +6,17 @@ import ( ) const ( - keyCompactionCache = "db.%v.compaction.cache" - keyCompactionNodeSize = "db.%v.compaction.node_size" - keyCompactionPageSize = "db.%v.compaction.page_size" + keyCompactionCache = "db.%v.compaction.cache" + keyCompactionNodeSize = "db.%v.compaction.node_size" + keyCompactionPageSize = "db.%v.compaction.page_size" keyCompactionPageChecksum = "db.%v.compaction.page_checksum" keyCompactionExpirePeriod = "db.%v.compaction.expire_period" - keyCompactionGCWatermark = "db.%v.compaction.gc_wm" - keyCompactionGCPeriod = "db.%v.compaction.gc_period" - keyMmap = "db.%v.mmap" - keyCompression = "db.%v.compression" - keyDirectIO = "db.%v.direct_io" - keySync = "db.%v.sync" + keyCompactionGCWatermark = "db.%v.compaction.gc_wm" + keyCompactionGCPeriod = "db.%v.compaction.gc_period" + keyMmap = "db.%v.mmap" + keyCompression = "db.%v.compression" + keyDirectIO = "db.%v.direct_io" + keySync = "db.%v.sync" ) // DatabaseConfig a structure for the description of the database to be created. @@ -95,7 +95,7 @@ func (db *Database) Document() *Document { } // Cursor returns a Cursor for iterating over rows in the database -func (db *Database) Cursor(doc *Document) (Cursor, error) { +func (db *Database) Cursor(doc *Document) (*Cursor, error) { if nil == doc { return nil, errors.New("failed to create cursor: nil Document") } @@ -103,9 +103,8 @@ func (db *Database) Cursor(doc *Document) (Cursor, error) { if nil == cPtr { return nil, fmt.Errorf("failed to create cursor: err=%v", db.env.Error()) } - cur := &cursor{ + return &Cursor{ ptr: cPtr, doc: doc, - } - return cur, nil + }, nil } diff --git a/database_test.go b/database_test.go index 13d69a6..2a01426 100644 --- a/database_test.go +++ b/database_test.go @@ -2,11 +2,12 @@ package sophia import ( "fmt" - "math" "math/rand" "os" "testing" + "io/ioutil" + "github.com/stretchr/testify/require" ) @@ -14,299 +15,466 @@ import ( // - test more settings for environment // - test more settings for database -const ( - KeyTemplate = "key%v" - ValueTemplate = "value%v" - - DBPath = "sophia" - DBName = "test" - RecordsCount = 500000 +func TestDatabaseDocument(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - RecordsCountBench = 5000000 -) + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) -func TestDatabaseCRUD(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() - var env *Environment - var db *Database + require.True(t, env.SetString(EnvironmentPath, tmpDir)) - if !t.Run("New Environment", func(t *testing.T) { env = testNewEnvironment(t) }) { - t.Fatal("Failed to create environment object") - } - defer func() { require.Nil(t, env.Close()) }() + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) - if !t.Run("New Database", func(t *testing.T) { db = testNewDatabase(t, env) }) { - t.Fatalf("Failed to create database object: %v", env.Error()) - } + require.Nil(t, env.Open()) + defer env.Close() - if !t.Run("Set", func(t *testing.T) { testSet(t, db) }) { - t.Fatalf("Set operations are failed: %v", env.Error()) - } - if !t.Run("Get", func(t *testing.T) { testGet(t, db) }) { - t.Fatalf("Get operations are failed: %v", env.Error()) - } - if !t.Run("Detele", func(t *testing.T) { testDelete(t, db) }) { - t.Fatalf("FDelete operations are failed: %v", env.Error()) - } + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) } -func testNewEnvironment(t *testing.T) *Environment { +func TestDatabaseSetInClosedEnvironment(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - return env + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.SetString(keyPath, expectedKey)) + require.True(t, doc.SetString(valuePath, expectedValue)) + + require.NotNil(t, db.Set(doc)) } -func testNewDatabase(t *testing.T, env *Environment) *Database { - require.True(t, env.Set("sophia.path", DBPath)) +func TestDatabaseSet(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeString)) - require.Nil(t, schema.AddValue("value", FieldTypeString)) + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, - Schema: schema, + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", }) require.Nil(t, err) require.NotNil(t, db) + require.Nil(t, env.Open()) - return db -} + defer env.Close() -func testSet(t *testing.T, db *Database) { - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i))) + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() - require.Nil(t, db.Set(doc)) - doc.Free() - } -} + require.True(t, doc.SetString(keyPath, expectedKey)) + require.True(t, doc.SetString(valuePath, expectedValue)) -func testGet(t *testing.T, db *Database) { - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - d, err := db.Get(doc) - doc.Free() - require.NotNil(t, d) - require.Nil(t, err) - var size int - require.Equal(t, fmt.Sprintf(KeyTemplate, i), d.GetString("key", &size)) - require.Equal(t, fmt.Sprintf(ValueTemplate, i), d.GetString("value", &size)) - d.Destroy() - } + require.Nil(t, db.Set(doc)) } -func testDelete(t *testing.T, db *Database) { - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.Nil(t, db.Delete(doc)) - doc.Free() - } +func TestDatabaseGetFromClosedEnvironment(t *testing.T) { + const keyPath = "key" + const expectedKey = "key1" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - d, err := db.Get(doc) - doc.Free() - require.Nil(t, d) - require.NotNil(t, err) - } + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.SetString(keyPath, expectedKey)) + + d, err := db.Get(doc) + require.NotNil(t, err) + require.Nil(t, d) } -func TestSchemaDupKey(t *testing.T) { - schema := Schema{} - keyName := "key" - require.Nil(t, schema.AddKey(keyName, FieldTypeString)) +func TestDatabaseGet(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - require.Len(t, schema.keys, 1) - require.Len(t, schema.keysNames, 1) + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) - require.Len(t, schema.values, 0) - require.Len(t, schema.valuesNames, 0) + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) - require.Equal(t, FieldTypeString, schema.keys[keyName]) - require.Equal(t, keyName, schema.keysNames[0]) + require.Nil(t, env.Open()) + defer env.Close() - require.NotNil(t, schema.AddKey(keyName, FieldTypeString)) + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) - require.Len(t, schema.keys, 1) - require.Len(t, schema.keysNames, 1) + require.True(t, doc.SetString(keyPath, expectedKey)) + require.True(t, doc.SetString(valuePath, expectedValue)) - require.Len(t, schema.values, 0) - require.Len(t, schema.valuesNames, 0) + require.Nil(t, db.Set(doc)) + doc.Free() - require.Equal(t, FieldTypeString, schema.keys[keyName]) - require.Equal(t, keyName, schema.keysNames[0]) + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.SetString(keyPath, expectedKey)) + + d, err := db.Get(doc) + require.Nil(t, err) + require.NotNil(t, d) + d.Destroy() +} + +func TestDatabaseDeleteFromClosedEnvironment(t *testing.T) { + const keyPath = "key" + const expectedKey = "key1" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.SetString(keyPath, expectedKey)) + + require.NotNil(t, db.Delete(doc)) } -func TestSchemaDupValue(t *testing.T) { - schema := Schema{} - valueName := "key" - require.Nil(t, schema.AddValue(valueName, FieldTypeString)) +func TestDatabaseDelete(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - require.Len(t, schema.keys, 0) - require.Len(t, schema.keysNames, 0) + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) - require.Len(t, schema.values, 1) - require.Len(t, schema.valuesNames, 1) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) - require.Equal(t, FieldTypeString, schema.values[valueName]) - require.Equal(t, valueName, schema.valuesNames[0]) + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) - require.NotNil(t, schema.AddValue(valueName, FieldTypeString)) + require.Nil(t, env.Open()) + defer env.Close() - require.Len(t, schema.keys, 0) - require.Len(t, schema.keysNames, 0) + doc := db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) - require.Len(t, schema.values, 1) - require.Len(t, schema.valuesNames, 1) + require.True(t, doc.SetString(keyPath, expectedKey)) + require.True(t, doc.SetString(valuePath, expectedValue)) - require.Equal(t, FieldTypeString, schema.values[valueName]) - require.Equal(t, valueName, schema.valuesNames[0]) + require.Nil(t, db.Set(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + + require.True(t, doc.SetString(keyPath, expectedKey)) + + require.Nil(t, db.Delete(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.SetString(keyPath, expectedKey)) + + d, err := db.Get(doc) + require.NotNil(t, err) + require.Equal(t, ErrNotFound, err) + require.Nil(t, d) } -func TestSetIntKV(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() +func TestDatabaseWithCustomSchema(t *testing.T) { + const keyPath = "custom_key" + const valuePath = "custom_value" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - defer func() { - require.Nil(t, env.Close()) - }() - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeUInt32)) - require.Nil(t, schema.AddValue("value", FieldTypeUInt32)) + require.Nil(t, schema.AddKey(keyPath, FieldTypeUInt32)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeUInt32)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, }) require.Nil(t, err) require.NotNil(t, db) + require.Nil(t, env.Open()) + defer env.Close() - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", int64(i))) - require.True(t, doc.Set("value", int64(i))) + const expectedKey int64 = 42 + const expectedValue int64 = 73 - require.Nil(t, db.Set(doc)) - doc.Free() - } - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", int64(i))) - d, err := db.Get(doc) - doc.Free() - require.Nil(t, err) - require.NotNil(t, d) - require.Equal(t, int64(i), d.GetInt("key")) - require.Equal(t, int64(i), d.GetInt("value")) - d.Destroy() - d.Free() - } + doc := db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue)) + + err = db.Set(doc) + doc.Free() + require.Nil(t, err) + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + + d, err := db.Get(doc) + doc.Free() + require.Nil(t, err) + require.NotNil(t, d) + + defer d.Destroy() + + require.Equal(t, expectedKey, d.GetInt(keyPath)) + require.Equal(t, expectedValue, d.GetInt(valuePath)) + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + + require.True(t, doc.Set(keyPath, expectedKey)) + + require.Nil(t, db.Delete(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.Set(keyPath, expectedKey)) + + d, err = db.Get(doc) + require.NotNil(t, err) + require.Equal(t, ErrNotFound, err) + require.Nil(t, d) } -func TestSetMultiKey(t *testing.T) { - defer func() { require.Nil(t, os.RemoveAll(DBPath)) }() +func TestDatabaseWithMultipleKeys(t *testing.T) { + const ( + key1Path = "key1" + key2Path = "key2" + key3Path = "key3" + valuePath = "value" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - defer func() { require.Nil(t, env.Close()) }() - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeUInt32)) - require.Nil(t, schema.AddKey("key_j", FieldTypeUInt32)) - require.Nil(t, schema.AddKey("key_k", FieldTypeUInt32)) - require.Nil(t, schema.AddValue("value", FieldTypeUInt64)) + require.Nil(t, schema.AddKey(key1Path, FieldTypeUInt32)) + require.Nil(t, schema.AddKey(key2Path, FieldTypeUInt32)) + require.Nil(t, schema.AddKey(key3Path, FieldTypeUInt32)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeUInt64)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, }) require.Nil(t, err) require.NotNil(t, db) + require.Nil(t, env.Open()) + defer env.Close() - count := int(math.Pow(RecordsCount, 1/3)) - - for i := 0; i < count; i++ { - for j := 0; j < count; j++ { - for k := 0; k < count; k++ { - doc := db.Document() - require.True(t, doc.Set("key", i)) - require.True(t, doc.Set("key_j", uint64(j))) - require.True(t, doc.Set("key_k", uint32(k))) - require.True(t, doc.Set("value", i)) - - require.Nil(t, db.Set(doc)) - doc.Free() - } - } - } - for i := 0; i < count; i++ { - for j := 0; j < count; j++ { - for k := 0; k < count; k++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", int64(i))) - require.True(t, doc.Set("key_j", int64(j))) - require.True(t, doc.Set("key_k", int64(k))) - d, err := db.Get(doc) - doc.Free() - require.Nil(t, err) - require.NotNil(t, d) - require.Equal(t, int64(i), d.GetInt("key")) - require.Equal(t, int64(j), d.GetInt("key_j")) - require.Equal(t, int64(k), d.GetInt("key_k")) - require.Equal(t, int64(i), d.GetInt("value")) - d.Destroy() - d.Free() - } - } - } + const ( + expectedKey1 int64 = 4 + expectedKey2 int64 = 8 + expectedKey3 int64 = 15 + expectedValue int64 = 16 + ) + + doc := db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(key1Path, expectedKey1)) + require.True(t, doc.Set(key2Path, expectedKey2)) + require.True(t, doc.Set(key3Path, expectedKey3)) + require.True(t, doc.Set(valuePath, expectedValue)) + + err = db.Set(doc) + doc.Free() + require.Nil(t, err) + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(key1Path, expectedKey1)) + require.True(t, doc.Set(key2Path, expectedKey2)) + require.True(t, doc.Set(key3Path, expectedKey3)) + + d, err := db.Get(doc) + doc.Free() + require.Nil(t, err) + require.NotNil(t, d) + + defer d.Destroy() + + require.Equal(t, expectedKey1, d.GetInt(key1Path)) + require.Equal(t, expectedKey2, d.GetInt(key2Path)) + require.Equal(t, expectedKey3, d.GetInt(key3Path)) + require.Equal(t, expectedValue, d.GetInt(valuePath)) + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + + require.True(t, doc.Set(key1Path, expectedKey1)) + require.True(t, doc.Set(key2Path, expectedKey2)) + require.True(t, doc.Set(key3Path, expectedKey3)) + + require.Nil(t, db.Delete(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.Nil(t, env.Error()) + defer doc.Free() + + require.True(t, doc.Set(key1Path, expectedKey1)) + require.True(t, doc.Set(key2Path, expectedKey2)) + require.True(t, doc.Set(key3Path, expectedKey3)) + + d, err = db.Get(doc) + require.NotNil(t, err) + require.Equal(t, ErrNotFound, err) + require.Nil(t, d) } func TestDatabaseUseSomeDocumentsAtTheSameTime(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() + const ( + keyPath = "key" + valuePath = "value" + expectedKey1 = "key1" + expectedValue1 = "value1" + expectedKey2 = "key2" + expectedValue2 = "value2" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) - schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeString)) - require.Nil(t, schema.AddValue("value", FieldTypeString)) - - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, - Schema: schema, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", }) require.Nil(t, err) require.NotNil(t, db) + require.Nil(t, env.Open()) + defer env.Close() doc1 := db.Document() doc2 := db.Document() @@ -314,106 +482,122 @@ func TestDatabaseUseSomeDocumentsAtTheSameTime(t *testing.T) { require.NotNil(t, doc1) require.NotNil(t, doc2) - defer func() { - doc1.Free() - doc2.Free() - }() + require.True(t, doc1.Set(keyPath, expectedKey1)) + require.True(t, doc1.Set(valuePath, expectedValue1)) + + require.True(t, doc2.Set(keyPath, expectedKey2)) + require.True(t, doc2.Set(valuePath, expectedValue2)) - require.True(t, doc1.Set("key", "key1")) - require.True(t, doc1.Set("value", "value1")) - require.True(t, doc2.Set("key", "key2")) - require.True(t, doc2.Set("value", "value2")) + require.Nil(t, db.Set(doc1)) + doc1.Free() - db.Set(doc1) - db.Set(doc2) + require.Nil(t, db.Set(doc2)) + doc2.Free() doc := db.Document() require.NotNil(t, doc) - doc.Set("key", "key1") + doc.Set(keyPath, expectedKey1) d, err := db.Get(doc) + doc.Free() require.NotNil(t, d) require.Nil(t, err) size := 0 - require.Equal(t, "value1", d.GetString("value", &size)) - require.Equal(t, 6, size) + require.Equal(t, expectedValue1, d.GetString(valuePath, &size)) + require.Equal(t, len(expectedValue1), size) d.Destroy() doc = db.Document() require.NotNil(t, doc) - doc.Set("key", "key2") + doc.Set(keyPath, expectedKey2) d, err = db.Get(doc) + doc.Free() require.NotNil(t, d) require.Nil(t, err) size = 0 - require.Equal(t, "value2", d.GetString("value", &size)) - require.Equal(t, 6, size) + require.Equal(t, expectedValue2, d.GetString(valuePath, &size)) + require.Equal(t, len(expectedValue1), size) d.Destroy() } func TestDatabaseDeleteNotExistingKey(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() + const keyPath = "key" + const expectedKey = "key1" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) - schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeString)) - require.Nil(t, schema.AddValue("value", FieldTypeString)) - - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, - Schema: schema, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", }) require.Nil(t, err) require.NotNil(t, db) + require.Nil(t, env.Open()) + defer env.Close() doc := db.Document() defer doc.Free() - doc.Set("key", "key1") + doc.Set(keyPath, expectedKey) require.Nil(t, db.Delete(doc)) } // ATTN - This benchmark don't show real performance // It is just a long running tests func BenchmarkDatabaseSet(b *testing.B) { - defer func() { - require.Nil(b, os.RemoveAll(DBPath)) - }() + const ( + keyPath = "key" + valuePath = "value" + KeyTemplate = "key%013v" + ValueTemplate = "value%011v" + RecordsCountBench = 500000 + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(b, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(b, err) require.NotNil(b, env) - require.True(b, env.Set("sophia.path", DBPath)) - - schema := &Schema{} - require.Nil(b, schema.AddKey("key", FieldTypeString)) - require.Nil(b, schema.AddValue("value", FieldTypeString)) + require.True(b, env.SetString(EnvironmentPath, tmpDir)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, - Schema: schema, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", }) require.Nil(b, err) require.NotNil(b, db) + require.Nil(b, env.Open()) + defer env.Close() + + indices := rand.Perm(RecordsCountBench) + + type pair struct { + key string + value string + } - indices := rand.Perm(b.N) - keys := make(map[string]string) + pairs := make([]pair, 0, RecordsCountBench) for _, i := range indices { - keys[fmt.Sprintf(KeyTemplate, i)] = fmt.Sprintf(ValueTemplate, i) + pairs = append(pairs, pair{ + key: fmt.Sprintf(KeyTemplate, i), + value: fmt.Sprintf(ValueTemplate, i), + }) } b.ResetTimer() - for key, value := range keys { + for i := 0; i < b.N; i++ { + index := i % RecordsCountBench doc := db.Document() - require.True(b, doc.Set("key", key)) - require.True(b, doc.Set("value", value)) + require.True(b, doc.Set(keyPath, pairs[index].key)) + require.True(b, doc.Set(valuePath, pairs[index].value)) require.Nil(b, db.Set(doc)) doc.Free() } @@ -422,50 +606,65 @@ func BenchmarkDatabaseSet(b *testing.B) { // ATTN - This benchmark don't show real performance // It is just a long running tests func BenchmarkDatabaseGet(b *testing.B) { - defer func() { - require.Nil(b, os.RemoveAll(DBPath)) - }() + const ( + keyPath = "key" + valuePath = "value" + KeyTemplate = "key%013v" + ValueTemplate = "value%011v" + RecordsCountBench = 500000 + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(b, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(b, err) require.NotNil(b, env) - require.True(b, env.Set("sophia.path", DBPath)) - - schema := &Schema{} - require.Nil(b, schema.AddKey("key", FieldTypeString)) - require.Nil(b, schema.AddValue("value", FieldTypeString)) + require.True(b, env.SetString(EnvironmentPath, tmpDir)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, - Schema: schema, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", }) require.Nil(b, err) require.NotNil(b, db) + require.Nil(b, env.Open()) + defer env.Close() - for i := 0; i < RecordsCountBench; i++ { - doc := db.Document() - require.True(b, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(b, doc.Set("value", fmt.Sprintf(ValueTemplate, i))) - err = db.Set(doc) - require.Nil(b, err) - doc.Free() + indices := rand.Perm(RecordsCountBench) + + type pair struct { + key string + value string } - indices := rand.Perm(b.N) - keys := make(map[string]string) + pairs := make([]pair, 0, RecordsCountBench) for _, i := range indices { - keys[fmt.Sprintf(KeyTemplate, i)] = fmt.Sprintf(ValueTemplate, i) + pairs = append(pairs, pair{ + key: fmt.Sprintf(KeyTemplate, i), + value: fmt.Sprintf(ValueTemplate, i), + }) } + + for _, pair := range pairs { + doc := db.Document() + require.True(b, doc.Set(keyPath, pair.key)) + require.True(b, doc.Set(valuePath, pair.value)) + require.Nil(b, db.Set(doc)) + doc.Free() + } + var size int b.ResetTimer() - for key, value := range keys { + for i := 0; i < b.N; i++ { + index := i % RecordsCountBench doc := db.Document() - require.True(b, doc.Set("key", key)) + require.True(b, doc.Set(keyPath, pairs[index].key)) d, err := db.Get(doc) - require.Nil(b, err) - require.Equal(b, value, d.GetString("value", &size)) doc.Free() + require.Nil(b, err) + require.Equal(b, pairs[index].value, d.GetString(valuePath, &size)) d.Destroy() } } diff --git a/environment.go b/environment.go index 9cbb0c9..eee1f44 100644 --- a/environment.go +++ b/environment.go @@ -6,6 +6,7 @@ import ( ) const errorPath = "sophia.error" +const EnvironmentPath = "sophia.path" var ErrEnvironmentClosed = errors.New("usage of closed environment") @@ -29,13 +30,10 @@ func NewEnvironment() (*Environment, error) { // NewDatabase creates new database in environment with given configuration. // At least database's name should be defined. Another options aren't required. // Database configuration can't be changed after Environment's Open() was called. -func (env *Environment) NewDatabase(config *DatabaseConfig) (*Database, error) { +func (env *Environment) NewDatabase(config DatabaseConfig) (*Database, error) { if env.ptr == nil { return nil, ErrEnvironmentClosed } - if config == nil { - return nil, errors.New("illegal configuration: nil configuration") - } if config.DirectIO && !config.DisableMmapMode { return nil, errors.New("illegal configuration: both direct_io and mmap is enabled") @@ -104,7 +102,7 @@ func (env *Environment) initializeSchema(name string, schema *Schema) int { return i } -func (env *Environment) configureCompaction(config *DatabaseConfig) { +func (env *Environment) configureCompaction(config DatabaseConfig) { if config.CompactionCacheSize != 0 { env.SetInt(fmt.Sprintf(keyCompactionCache, config.Name), config.CompactionCacheSize) } diff --git a/environment_test.go b/environment_test.go index 0c085ab..3a386a6 100644 --- a/environment_test.go +++ b/environment_test.go @@ -13,16 +13,8 @@ func TestNewEnvironment(t *testing.T) { env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) -} - -func TestEnvironmentNewDatabaseNilConfig(t *testing.T) { - env, err := NewEnvironment() - require.Nil(t, err) - require.NotNil(t, env) - db, err := env.NewDatabase(nil) - require.NotNil(t, err) - require.Nil(t, db) + require.Nil(t, env.Error()) } func TestEnvironmentNewDatabaseEmptyConfig(t *testing.T) { @@ -30,7 +22,7 @@ func TestEnvironmentNewDatabaseEmptyConfig(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - db, err := env.NewDatabase(&DatabaseConfig{}) + db, err := env.NewDatabase(DatabaseConfig{}) require.NotNil(t, err) require.Nil(t, db) } @@ -40,7 +32,7 @@ func TestEnvironmentNewDatabaseIllegalConfig(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - db, err := env.NewDatabase(&DatabaseConfig{ + db, err := env.NewDatabase(DatabaseConfig{ Name: "test", DirectIO: true, }) @@ -53,7 +45,7 @@ func TestEnvironmentNewDatabaseIllegalName(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - db, err := env.NewDatabase(&DatabaseConfig{ + db, err := env.NewDatabase(DatabaseConfig{ Name: "test.test", }) @@ -66,7 +58,7 @@ func TestEnvironmentNewDatabaseDefaultSchema(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - db, err := env.NewDatabase(&DatabaseConfig{ + db, err := env.NewDatabase(DatabaseConfig{ Name: "test", }) require.Nil(t, err) @@ -80,8 +72,7 @@ func TestEnvironmentEmptyPath(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - err = env.Open() - require.NotNil(t, err) + require.NotNil(t, env.Open()) } func TestEnvironmentOpenWithoutDatabase(t *testing.T) { @@ -89,7 +80,7 @@ func TestEnvironmentOpenWithoutDatabase(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", "test")) + require.True(t, env.Set(EnvironmentPath, "test")) require.NotNil(t, env.Open()) } @@ -106,9 +97,11 @@ func TestEnvironmentCloseTwice(t *testing.T) { require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", dbPath)) + require.True(t, env.Set(EnvironmentPath, dbPath)) - db, err := env.NewDatabase(&DatabaseConfig{Name: "test"}) + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test", + }) require.Nil(t, err) require.NotNil(t, db) diff --git a/schema.go b/schema.go index 19337c8..bb8564f 100644 --- a/schema.go +++ b/schema.go @@ -26,7 +26,6 @@ func (s *Schema) AddKey(name string, typ FieldType) error { return nil } - // AddKey adds new value field for record. // If record have already had field with such name error will be returned func (s *Schema) AddValue(name string, typ FieldType) error { @@ -44,6 +43,6 @@ func (s *Schema) AddValue(name string, typ FieldType) error { func defaultSchema() *Schema { schema := &Schema{} schema.AddKey("key", FieldTypeString) - schema.AddKey("value", FieldTypeString) + schema.AddValue("value", FieldTypeString) return schema } diff --git a/schema_test.go b/schema_test.go new file mode 100644 index 0000000..6b829d9 --- /dev/null +++ b/schema_test.go @@ -0,0 +1,59 @@ +package sophia + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSchemaDupKey(t *testing.T) { + schema := Schema{} + keyName := "key" + require.Nil(t, schema.AddKey(keyName, FieldTypeString)) + + require.Len(t, schema.keys, 1) + require.Len(t, schema.keysNames, 1) + + require.Len(t, schema.values, 0) + require.Len(t, schema.valuesNames, 0) + + require.Equal(t, FieldTypeString, schema.keys[keyName]) + require.Equal(t, keyName, schema.keysNames[0]) + + require.NotNil(t, schema.AddKey(keyName, FieldTypeString)) + + require.Len(t, schema.keys, 1) + require.Len(t, schema.keysNames, 1) + + require.Len(t, schema.values, 0) + require.Len(t, schema.valuesNames, 0) + + require.Equal(t, FieldTypeString, schema.keys[keyName]) + require.Equal(t, keyName, schema.keysNames[0]) +} + +func TestSchemaDupValue(t *testing.T) { + schema := Schema{} + valueName := "key" + require.Nil(t, schema.AddValue(valueName, FieldTypeString)) + + require.Len(t, schema.keys, 0) + require.Len(t, schema.keysNames, 0) + + require.Len(t, schema.values, 1) + require.Len(t, schema.valuesNames, 1) + + require.Equal(t, FieldTypeString, schema.values[valueName]) + require.Equal(t, valueName, schema.valuesNames[0]) + + require.NotNil(t, schema.AddValue(valueName, FieldTypeString)) + + require.Len(t, schema.keys, 0) + require.Len(t, schema.keysNames, 0) + + require.Len(t, schema.values, 1) + require.Len(t, schema.valuesNames, 1) + + require.Equal(t, FieldTypeString, schema.values[valueName]) + require.Equal(t, valueName, schema.valuesNames[0]) +} diff --git a/transaction_test.go b/transaction_test.go index e3510ed..545efaa 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -1,168 +1,285 @@ package sophia import ( - "fmt" + "io/ioutil" "os" "testing" "github.com/stretchr/testify/require" ) -func TestDatabaseTx(t *testing.T) { - defer func() { require.Nil(t, os.RemoveAll(DBPath)) }() - var ( - env *Environment - db *Database +func TestTxSet(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) - if !t.Run("New Environment", func(t *testing.T) { env = testNewEnvironment(t) }) { - t.Fatal("Failed to create environment object") - } - defer func() { require.Nil(t, env.Close()) }() - - if !t.Run("New Database", func(t *testing.T) { db = testNewDatabase(t, env) }) { - t.Fatal("Failed to create database object") - } - - if !t.Run("Set", func(t *testing.T) { testSetTx(t, env, db) }) { - t.Fatal("Set operations are failed") - } - t.Run("Get", func(t *testing.T) { testGetTx(t, env, db) }) - t.Run("Detele", func(t *testing.T) { testDeleteTx(t, env, db) }) - t.Run("Rollback", func(t *testing.T) { testTxRollback(t, env, db) }) - t.Run("Concurrent", func(t *testing.T) { testConcurrentTx(t, env, db) }) -} + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() -func testSetTx(t *testing.T, env *Environment, db *Database) { tx, err := env.BeginTx() require.Nil(t, err) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i))) - require.Nil(t, tx.Set(doc)) - doc.Free() - } + doc := db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue)) + + require.Nil(t, tx.Set(doc)) + doc.Free() + require.Equal(t, TxOk, tx.Commit()) } -func testGetTx(t *testing.T, env *Environment, db *Database) { +func TestTxGet(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() + + doc := db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue)) + + require.Nil(t, db.Set(doc)) + doc.Free() + tx, err := env.BeginTx() require.Nil(t, err) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - d, err := tx.Get(doc) - doc.Free() - require.NotNil(t, d) - require.Nil(t, err) - var size int - require.Equal(t, fmt.Sprintf(KeyTemplate, i), d.GetString("key", &size)) - require.Equal(t, fmt.Sprintf(ValueTemplate, i), d.GetString("value", &size)) - d.Destroy() - d.Free() - } + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + + d, err := tx.Get(doc) + doc.Free() + + require.NotNil(t, d) + require.Nil(t, err) + + var size int + require.Equal(t, expectedKey, d.GetString(keyPath, &size)) + require.Equal(t, expectedValue, d.GetString(valuePath, &size)) + d.Destroy() + require.Equal(t, TxOk, tx.Commit()) } -func testDeleteTx(t *testing.T, env *Environment, db *Database) { +func TestTxDelete(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() + + doc := db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue)) + + require.Nil(t, db.Set(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + + d, err := db.Get(doc) + doc.Free() + require.NotNil(t, d) + require.Nil(t, err) + + var size int + require.Equal(t, expectedKey, d.GetString(keyPath, &size)) + require.Equal(t, expectedValue, d.GetString(valuePath, &size)) + d.Destroy() + tx, err := env.BeginTx() require.Nil(t, err) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.Nil(t, tx.Delete(doc)) - doc.Free() - } - - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.NotNil(t, doc) - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - d, err := tx.Get(doc) - doc.Free() - require.Nil(t, d) - require.NotNil(t, err) - } + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + require.Nil(t, tx.Delete(doc)) + doc.Free() + + doc = db.Document() + require.NotNil(t, doc) + require.True(t, doc.Set(keyPath, expectedKey)) + d, err = tx.Get(doc) + doc.Free() + require.Nil(t, d) + require.NotNil(t, err) + require.Equal(t, TxOk, tx.Commit()) } -func testTxRollback(t *testing.T, env *Environment, db *Database) { +func TestTxRollback(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + expectedValue = "value1" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) + + require.Nil(t, env.Open()) + defer env.Close() + tx, err := env.BeginTx() require.Nil(t, err) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i))) + doc := db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue)) - require.Nil(t, tx.Set(doc)) - doc.Free() - } + require.Nil(t, tx.Set(doc)) + doc.Free() require.Nil(t, tx.Rollback()) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) + doc = db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) - d, err := db.Get(doc) - require.Nil(t, d) - require.Equal(t, ErrNotFound, err) - doc.Free() - } + d, err := db.Get(doc) + require.Nil(t, d) + require.Equal(t, ErrNotFound, err) + doc.Free() } -func testConcurrentTx(t *testing.T, env *Environment, db *Database) { - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i))) +func TestConcurrentTx(t *testing.T) { + const ( + keyPath = "key" + valuePath = "value" + expectedKey = "key1" + initialValue = "value1" + expectedValue1 = "value2" + expectedValue2 = "value3" + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + + env, err := NewEnvironment() + require.Nil(t, err) + require.NotNil(t, env) + + require.True(t, env.SetString(EnvironmentPath, tmpDir)) + + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", + }) + require.Nil(t, err) + require.NotNil(t, db) - require.Nil(t, db.Set(doc)) - doc.Free() - } + require.Nil(t, env.Open()) + defer env.Close() + + doc := db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, initialValue)) + + require.Nil(t, db.Set(doc)) + doc.Free() tx1, err := env.BeginTx() require.Nil(t, err) tx2, err := env.BeginTx() require.Nil(t, err) - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i+1))) + doc = db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue1)) - require.Nil(t, tx1.Set(doc)) - doc.Free() - } + require.Nil(t, tx1.Set(doc)) + doc.Free() - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - require.True(t, doc.Set("value", fmt.Sprintf(ValueTemplate, i+2))) + doc = db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + require.True(t, doc.Set(valuePath, expectedValue2)) - require.Nil(t, tx2.Set(doc)) - doc.Free() - } + require.Nil(t, tx2.Set(doc)) + doc.Free() require.Equal(t, TxOk, tx1.Commit()) require.Equal(t, TxRollback, tx2.Commit()) var size int - for i := 0; i < RecordsCount; i++ { - doc := db.Document() - require.True(t, doc.Set("key", fmt.Sprintf(KeyTemplate, i))) - - d, err := db.Get(doc) - require.Nil(t, err) - require.NotNil(t, d) - value := d.GetString("value", &size) - require.Equal(t, fmt.Sprintf(ValueTemplate, i+1), value) - doc.Free() - d.Free() - d.Destroy() - } + doc = db.Document() + require.True(t, doc.Set(keyPath, expectedKey)) + + d, err := db.Get(doc) + doc.Free() + require.Nil(t, err) + require.NotNil(t, d) + value := d.GetString(valuePath, &size) + require.Equal(t, expectedValue1, value) + d.Destroy() } diff --git a/upsert_test.go b/upsert_test.go index 11c4028..3b06f2e 100644 --- a/upsert_test.go +++ b/upsert_test.go @@ -5,25 +5,30 @@ import ( "testing" "unsafe" + "io/ioutil" + "github.com/stretchr/testify/require" ) func TestDatabaseUpsert(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() + const keyPath = "key" + const valuePath = "id" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeUInt32)) - require.Nil(t, schema.AddValue("id", FieldTypeUInt32)) + require.Nil(t, schema.AddKey(keyPath, FieldTypeUInt32)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeUInt32)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, Upsert: upsertCallback, }) @@ -31,6 +36,7 @@ func TestDatabaseUpsert(t *testing.T) { require.NotNil(t, db) require.Nil(t, env.Open()) + defer env.Close() /* increment key 10 times */ const key uint32 = 1234 @@ -38,41 +44,45 @@ func TestDatabaseUpsert(t *testing.T) { var increment int64 = 1 for i := 0; i < iterations; i++ { doc := db.Document() - doc.Set("key", key) - doc.Set("id", increment) + doc.Set(keyPath, key) + doc.Set(valuePath, increment) require.Nil(t, db.Upsert(doc)) } /* get */ doc := db.Document() - doc.Set("key", key) + doc.Set(keyPath, key) result, err := db.Get(doc) require.Nil(t, err) require.NotNil(t, result) defer result.Destroy() - require.Equal(t, iterations*increment, result.GetInt("id")) + require.Equal(t, iterations*increment, result.GetInt(valuePath)) } func TestDatabaseUpsertWithArg(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() + const ( + keyPath = "key" + valuePath = "id" + upsertArg = 5 + ) + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeUInt32)) - require.Nil(t, schema.AddValue("id", FieldTypeUInt32)) - - const upsertArg = 5 + require.Nil(t, schema.AddKey(keyPath, FieldTypeUInt32)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeUInt32)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, Upsert: upsertCallbackWithArg, UpsertArg: upsertArg, @@ -81,6 +91,7 @@ func TestDatabaseUpsertWithArg(t *testing.T) { require.NotNil(t, db) require.Nil(t, env.Open()) + defer env.Close() /* increment key 10 times */ const key uint32 = 1234 @@ -102,7 +113,7 @@ func TestDatabaseUpsertWithArg(t *testing.T) { require.NotNil(t, result) defer result.Destroy() - expected := iterations*increment+upsertArg*(iterations-1) + expected := iterations*increment + upsertArg*(iterations-1) require.Equal(t, expected, result.GetInt("id")) } @@ -134,27 +145,31 @@ func upsertCallbackWithArg(count int, } func TestDatabaseUpsertError(t *testing.T) { - defer func() { - require.Nil(t, os.RemoveAll(DBPath)) - }() + const keyPath = "key" + const valuePath = "id" + tmpDir, err := ioutil.TempDir("", "sophia_test") + require.Nil(t, err) + defer os.RemoveAll(tmpDir) + env, err := NewEnvironment() require.Nil(t, err) require.NotNil(t, env) - require.True(t, env.Set("sophia.path", DBPath)) + require.True(t, env.SetString(EnvironmentPath, tmpDir)) schema := &Schema{} - require.Nil(t, schema.AddKey("key", FieldTypeUInt32)) - require.Nil(t, schema.AddValue("id", FieldTypeUInt32)) + require.Nil(t, schema.AddKey(keyPath, FieldTypeUInt32)) + require.Nil(t, schema.AddValue(valuePath, FieldTypeUInt32)) - db, err := env.NewDatabase(&DatabaseConfig{ - Name: DBName, + db, err := env.NewDatabase(DatabaseConfig{ + Name: "test_database", Schema: schema, }) require.Nil(t, err) require.NotNil(t, db) require.Nil(t, env.Open()) + defer env.Close() doc := db.Document() require.NotNil(t, doc) require.True(t, doc.Set("key", 1))