Skip to content

Commit

Permalink
feat: wip to enforce key typing
Browse files Browse the repository at this point in the history
  • Loading branch information
alextekartik committed Aug 20, 2024
1 parent fef587c commit 0d66b83
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 156 deletions.
32 changes: 18 additions & 14 deletions sembast/lib/src/api/sembast.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
export 'package:sembast/src/api/boundary.dart';
export 'package:sembast/src/api/client.dart';
export 'package:sembast/src/api/codec.dart';
export 'package:sembast/src/api/database.dart';
export 'package:sembast/src/api/database_mode.dart';
export 'package:sembast/src/api/exception.dart';
export 'package:sembast/src/api/factory.dart';
export 'package:sembast/src/api/filter.dart';
export 'package:sembast/src/api/finder.dart';
export 'package:sembast/src/api/query_ref.dart';
export 'package:sembast/src/api/boundary.dart' show Boundary;
export 'package:sembast/src/api/client.dart' show DatabaseClient;
export 'package:sembast/src/api/codec.dart' show SembastCodec;
export 'package:sembast/src/api/database.dart'
show Database, Field, FieldKey, FieldValue, DatabaseExtension;
export 'package:sembast/src/api/database_mode.dart' show DatabaseMode;
export 'package:sembast/src/api/exception.dart' show DatabaseException;
export 'package:sembast/src/api/factory.dart'
show DatabaseFactory, OnVersionChangedFunction;
export 'package:sembast/src/api/filter.dart'
show Filter, SembastFilterCombination;
export 'package:sembast/src/api/finder.dart' show Finder;
export 'package:sembast/src/api/query_ref.dart' show QueryRef;
export 'package:sembast/src/api/record_ref.dart' show RecordRef;
export 'package:sembast/src/type.dart' show RecordKeyBase, RecordValueBase;
export 'package:sembast/src/api/record_snapshot.dart'
show RecordSnapshot, RecordSnapshotIterableExtension;
export 'package:sembast/src/api/records_ref.dart';
export 'package:sembast/src/api/sort_order.dart';
export 'package:sembast/src/api/store_ref.dart';
export 'package:sembast/src/api/transaction.dart';
export 'package:sembast/src/api/records_ref.dart' show RecordsRef;
export 'package:sembast/src/api/sort_order.dart' show SortOrder;
export 'package:sembast/src/api/store_ref.dart'
show StoreRef, StoreFactory, intMapStoreFactory, stringMapStoreFactory;
export 'package:sembast/src/api/transaction.dart' show Transaction;
export 'package:sembast/src/cooperator.dart'
show enableSembastCooperator, disableSembastCooperator;

Expand Down
57 changes: 55 additions & 2 deletions sembast/lib/src/api/store_ref.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@ import 'package:sembast/src/import_common.dart';
import 'package:sembast/src/sembast_impl.dart' show dbMainStore;
import 'package:sembast/src/store_ref_impl.dart';

/// Don't throw exception yet. will be done in the future.
const checkStoreKeyThrowException = false;

/// Print a warning if a store key is not a String or an int, to enable in the future.
const checkStoreKey = true;

var _debugCheckStoreKeyPrinted = <String, bool>{};

bool _checkStoreKey<K>(String name) {
/// Type Object is supported for compatibility
if (K == String || K == int) {
return true;
}

final text = '''
*** WARNING ***
Invalid key type $K.
Only String and int are supported. See https://github.com/tekartik/sembast.dart/blob/master/sembast/README.md#keys for details
Recommendation is to create a store with an explicit type StoreRef<String, ...> or StoreRef<int, ...> or using intMapStoreFactor or stringMapStoreFactory
This will throw an exception in the future. For now it is displayed once per store.
''';
try {
throw ArgumentError(text);
} catch (e, st) {
if (checkStoreKeyThrowException) {
rethrow;
} else {
final printed = _debugCheckStoreKeyPrinted[name] ?? false;
if (!printed) {
_debugCheckStoreKeyPrinted[name] = true;
// ignore: avoid_print
print(text);
// ignore: avoid_print
print(st);
}
}
}
return true;
}

/// A pointer to a store.
///
abstract class StoreRef<K extends Key?, V extends Value?> {
Expand All @@ -20,10 +63,20 @@ abstract class StoreRef<K extends Key?, V extends Value?> {
/// A null name means a the main store.
///
/// A name must not start with `_` (besides the main store).
factory StoreRef(String name) => SembastStoreRef<K, V>(name);
factory StoreRef(String name) {
if (checkStoreKey) {
assert(_checkStoreKey<K>(dbMainStore));
}
return SembastStoreRef<K, V>(name);
}

/// A pointer to the main store
factory StoreRef.main() => SembastStoreRef<K, V>(dbMainStore);
factory StoreRef.main() {
if (checkStoreKey) {
assert(_checkStoreKey<K>(dbMainStore));
}
return SembastStoreRef<K, V>(dbMainStore);
}

/// Cast if needed
StoreRef<RK, RV> cast<RK extends Key?, RV extends Value?>();
Expand Down
4 changes: 2 additions & 2 deletions sembast/lib/src/database_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:sembast/sembast.dart';
import 'package:sembast/src/api/log_level.dart';
import 'package:sembast/src/api/protected/jdb.dart';
import 'package:sembast/src/api/protected/type.dart';
import 'package:sembast/src/api/v2/sembast.dart' as v2;
import 'package:sembast/src/async_content_codec.dart';
import 'package:sembast/src/changes_listener.dart';
import 'package:sembast/src/common_import.dart';
Expand All @@ -22,6 +21,7 @@ import 'package:sembast/src/sembast_codec_impl.dart';
import 'package:sembast/src/sembast_impl.dart';
import 'package:sembast/src/storage.dart';
import 'package:sembast/src/store_impl.dart';
import 'package:sembast/src/store_ref_impl.dart';
import 'package:sembast/src/transaction_impl.dart';
import 'package:sembast/src/utils.dart';
import 'package:synchronized/synchronized.dart';
Expand Down Expand Up @@ -694,7 +694,7 @@ class SembastDatabase extends Object
/// Get a store in a transaction.
SembastTransactionStore? txnGetStore(
SembastTransaction txn, String storeName) {
var store = getSembastStore(v2.StoreRef(storeName));
var store = getSembastStore(SembastStoreRef<Key?, Value?>(storeName));
return txn.toExecutor(store);
}

Expand Down
3 changes: 2 additions & 1 deletion sembast/lib/src/database_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:sembast/src/api/protected/database.dart';
import 'package:sembast/src/value_utils.dart';

import 'import_common.dart';
import 'store_ref_impl.dart';

/// Get the list of non empty store names.
Iterable<String> getNonEmptyStoreNames(Database database) =>
Expand Down Expand Up @@ -29,7 +30,7 @@ Future<void> databaseMerge(Database db,
/// Merge a given store in a transaction, assuming source database does not change
Future<void> txnMergeStore(Transaction txn,
{required Database sourceDatabase, required String storeName}) async {
var store = StoreRef<Key, Value>(storeName);
var store = SembastStoreRef<Key, Value>(storeName);
var originalRecords = await store.find(txn);
var originalMap = <dynamic, RecordSnapshot<Object, Object>>{
for (var v in originalRecords) v.ref.key: v
Expand Down
4 changes: 2 additions & 2 deletions sembast/lib/src/jdb/jdb_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import 'package:sembast/src/api/protected/codec.dart';
import 'package:sembast/src/api/protected/database.dart';
import 'package:sembast/src/api/protected/jdb.dart';
import 'package:sembast/src/api/protected/type.dart';
import 'package:sembast/src/api/store_ref.dart';
import 'package:sembast/src/env_utils.dart';
import 'package:sembast/src/store_ref_impl.dart';

/// Jdb.
abstract class JdbDatabase {
Expand Down Expand Up @@ -75,7 +75,7 @@ extension JdbDatabaseInternalExt on JdbDatabase {
JdbReadEntry _readEntryFromReadEntryEncoded(
JdbReadEntryEncoded encoded, Object? value) {
var id = encoded.id;
var store = StoreRef<Key?, Value?>(encoded.storeName);
var store = SembastStoreRef<Key?, Value?>(encoded.storeName);
var record = store.record(encoded.recordKey);
var deleted = encoded.deleted;
var entry = JdbReadEntry()
Expand Down
6 changes: 4 additions & 2 deletions sembast/lib/src/record_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:sembast/src/store_impl.dart';
import 'package:sembast/src/utils.dart';

import 'import_common.dart';
import 'store_ref_impl.dart';

///
/// Internal Record, either in store or in transaction
Expand Down Expand Up @@ -127,8 +128,9 @@ class ImmutableSembastRecord
/// Record from row map.
ImmutableSembastRecord.fromDatabaseRowMap(Map map) {
final storeName = map[dbStoreNameKey] as String?;
final storeRef =
storeName == null ? mainStoreRef : StoreRef<Key, Value>(storeName);
final storeRef = storeName == null
? mainStoreRef
: SembastStoreRef<Key, Value>(storeName);
var key = map[dbRecordKey] as Key?;
var value = map[dbRecordValueKey] as Value?;
if (key == null) {
Expand Down
12 changes: 10 additions & 2 deletions sembast/lib/src/sembast_impl.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'import_common.dart';
import 'store_ref_impl.dart';

/// The database version
const String dbVersionKey = 'version';
Expand All @@ -24,8 +25,15 @@ const String dbRecordDeletedKey = 'deleted'; // boolean
/// Main store.
const String dbMainStore = '_main'; // main store name;

/// Main store reference.
final mainStoreRef = StoreRef<Key, Value>(dbMainStore);
/// Main store reference. to deprecate since it is not typed
StoreRef<Key, Value> mainStoreRef = SembastStoreRef<Key, Value>(dbMainStore);

/// Main store reference, key as int, value untyped
StoreRef<int, Value> intMainStoreRef = SembastStoreRef<int, Value>(dbMainStore);

/// Main store reference, key as String, value untyped
StoreRef<String, Value> stringMainStoreRef =
SembastStoreRef<String, Value>(dbMainStore);

/// Jdb revision.
const String jdbRevisionKey = 'revision';
Expand Down
4 changes: 3 additions & 1 deletion sembast/lib/src/store_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:sembast/src/finder_impl.dart';
import 'package:sembast/src/key_utils.dart';
import 'package:sembast/src/record_impl.dart';
import 'package:sembast/src/sort.dart';
import 'package:sembast/src/store_ref_impl.dart';
import 'package:sembast/src/transaction_impl.dart';
import 'package:sembast/src/utils.dart';

Expand Down Expand Up @@ -57,7 +58,8 @@ class SembastStore {

// bool get isInTransaction => database.isInTransaction;
/// Store implementation.
SembastStore(this.database, String name) : ref = StoreRef<Key?, Value?>(name);
SembastStore(this.database, String name)
: ref = SembastStoreRef<Key?, Value?>(name);

/// The current transaction.
SembastTransaction? get currentTransaction => database.currentTransaction;
Expand Down
12 changes: 6 additions & 6 deletions sembast/lib/src/store_ref_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:sembast/src/record_impl.dart';
import 'package:sembast/src/record_ref_impl.dart';
import 'package:sembast/src/record_snapshot_impl.dart';
import 'package:sembast/src/records_ref_impl.dart';
import 'package:sembast/src/sembast_impl.dart';

import 'database_impl.dart';
import 'import_common.dart';
Expand All @@ -20,6 +21,11 @@ class SembastStoreRef<K, V> with StoreRefMixin<K, V> {
SembastStoreRef(String name) {
this.name = name;
}

/// Store implementation.
SembastStoreRef.main() {
name = dbMainStore;
}
}

/// Store ref mixin.
Expand Down Expand Up @@ -430,9 +436,3 @@ mixin StoreFactoryMixin<K, V> implements StoreFactory<K, V> {

/// Store factory base.
class StoreFactoryBase<K, V> with StoreFactoryMixin<K, V> {}

/// common `<int, Map<String, Object?>>` factory
final intMapStoreFactory = StoreFactoryBase<int, Map<String, Object?>>();

/// common `<String, Map<String, Object?>>` factory
final stringMapStoreFactory = StoreFactoryBase<String, Map<String, Object?>>();
5 changes: 3 additions & 2 deletions sembast/lib/utils/sembast_import_export.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:sembast/src/env_utils.dart';
import 'package:sembast/src/json_utils.dart';
import 'package:sembast/src/model.dart';
import 'package:sembast/src/store_impl.dart';
import 'package:sembast/src/store_ref_impl.dart';
import 'package:sembast/src/transaction_impl.dart';

const String _dbVersion = 'version';
Expand Down Expand Up @@ -240,8 +241,8 @@ Future<Database> importDatabase(
final keys = (storeExport[_keys] as Iterable).toList(growable: false);
final values = List<Object>.from(storeExport[_values] as Iterable);

var store =
(txn as SembastTransaction).getSembastStore(StoreRef(storeName));
var store = (txn as SembastTransaction)
.getSembastStore(SembastStoreRef(storeName));
for (var i = 0; i < keys.length; i++) {
var key = keys[i] as Object;
await store.txnPut(
Expand Down
4 changes: 2 additions & 2 deletions sembast/test/database_format_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ void defineTestsWithCodec(FileSystemTestContext ctx, {SembastCodec? codec}) {
test('1_record_in_2_stores', () async {
await prepareForDb();
final db = await factory.openDatabase(dbPath, codec: codec);
(db as SembastDatabase).getSembastStore(StoreRef('store1'));
db.getSembastStore(StoreRef('store2'));
(db as SembastDatabase).getSembastStore(StoreRef<int, String>('store1'));
db.getSembastStore(StoreRef<int, Object>('store2'));
await StoreRef<int, Object>('store2').record(1).put(db, 'hi');
await db.close();
final lines = await readContent(fs, dbPath);
Expand Down
3 changes: 2 additions & 1 deletion sembast/test/jdb_memory_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:sembast/src/api/protected/jdb.dart';
import 'package:sembast/src/api/protected/type.dart';
import 'package:sembast/src/jdb/jdb_factory_memory.dart';
import 'package:sembast/src/record_impl.dart';
import 'package:sembast/src/store_ref_impl.dart';

import 'jdb_test_common.dart';

Expand All @@ -18,7 +19,7 @@ class JdbWriteEntryMock extends JdbRawWriteEntry {
bool? deleted})
: super(
deleted: deleted ?? false,
record: StoreRef<Key?, Value?>.main().record(key));
record: SembastStoreRef<Key?, Value?>.main().record(key));
}

void main() {
Expand Down
17 changes: 16 additions & 1 deletion sembast/test/sembast_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var records = store.records([1, 2]);

void main() {
group('sembast_api', () {
test('public', () {
test('public', () async {
// What we want public
StoreRef;
RecordRef;
Expand Down Expand Up @@ -86,6 +86,21 @@ void main() {
SembastQueryRefSyncExtension(query).onSnapshotsSync;
SembastQueryRefSyncExtension(query).onSnapshotSync;
SembastQueryRefSyncExtension(query).onCountSync;

// ignore: unused_element
Future<void> ignored(Database db) async {
await DatabaseExtension(db).reload();
await DatabaseExtension(db).reOpen();
await DatabaseExtension(db).checkForChanges();
await DatabaseExtension(db).compact();
}

// ignore: unused_element
void ignoreSnapshots(List<RecordSnapshot> snapshots) {
RecordSnapshotIterableExtension(snapshots).keys;
RecordSnapshotIterableExtension(snapshots).values;
RecordSnapshotIterableExtension(snapshots).keysAndValues;
}
});
});
}
6 changes: 3 additions & 3 deletions sembast/test/sembast_import_export_io_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ void main() {
var file = join('test', 'src', 'data', 'export1.jsonl');
var db = await importDatabaseFromFile(
file, newDatabaseFactoryMemory(), 'test');
expect(await StoreRef.main().record(1).get(db), 'hi');
expect(await StoreRef<int, String>.main().record(1).get(db), 'hi');
});
test('export', () async {
var db = await newDatabaseFactoryMemory().openDatabase('src');
await StoreRef.main().record(1).put(db, 'test2');
await StoreRef<int, String>.main().record(1).put(db, 'test2');
var file = join('.local', 'test', 'export', 'export2.jsonl');
await exportDatabaseToJsonlFile(db, file);

db = await importDatabaseFromFile(
file, newDatabaseFactoryMemory(), 'test');
expect(await StoreRef.main().record(1).get(db), 'test2');
expect(await StoreRef<int, String>.main().record(1).get(db), 'test2');
});
});
}
Loading

0 comments on commit 0d66b83

Please sign in to comment.