Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Manager #2939

Merged
merged 83 commits into from
Apr 23, 2024
Merged

Add Manager #2939

merged 83 commits into from
Apr 23, 2024

Conversation

dickermoshe
Copy link
Collaborator

@dickermoshe dickermoshe commented Apr 2, 2024

Warning, The following proposed API has changed from the examples shown here, see the docs for an updated representation of the manager APi.

  • Docs
  • Tests (Lot's of em')

This introduces a simpler interface to access the database.
It makes it easier to create simple, or even some complex quiries and then directly read, watch, update or delete based on
them. It should also lower the barrier of entry for developers used to simpler database alternatives.

Managers

By default every generated database will have a new field managers, which exposes a manager for each table in the database.
Each manager is able to:

  • Create new instances: managers.categories.create((o) => o(name: 'test'))
  • Create new instances in bulk: managers.categories.bulkCreate((o) => [o(name: 'test1'),o(name: 'test2')])
  • Replace existing instaces: managers.categories.replace(category.copyWith(name:"Important"))

Filtering & Ordering

However, the real superpower is the packaged filter and ordering builder.
Here is an example schema to show how these filters work.

@DataClassName('TodoEntry')
class TodoEntries extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get description => text()();
  @ReferenceName("todos") // Optional: Custom name for the reverse reference 
  IntColumn get category => integer().nullable().references(Categories, #id)();
  DateTimeColumn get dueDate => dateTime().nullable()();
}

@DataClassName('Category')
class Categories extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  IntColumn get color => integer().map(const ColorConverter())();
}

class ColorConverter extends TypeConverter<Color, int> {
  const ColorConverter();
  @override
  Color fromSql(int fromDb) => Color(fromDb);
  @override
  int toSql(Color value) => value.value;
}
  1. Filter on all columns
// All TodoEntries before today
todoEntries.filter((f) => f.dueDate.isBefore(DateTime.now()))

// Todo Entry that matches id=5
todoEntries.filter((f) => f.id(5))
OR
todoEntries.filter((f) => f.id.equals(5))

// Filter on Custom Types
categories.filter((f) => f.color(Colors.white))

// Combine multiple filters using `&` and `|`
categories.filter((f) => f.color(Colors.white) & f.name("School"))
  1. Filter on references
// All Todo Entries whose categories are white
todoEntries.filter((f) => f.category((f) => f.color(Colors.white))

// Combine with local filters
// Todo Entries that are already due or whose categories are white
todoEntries.filter((f) =>
        f.dueDate.isBefore(DateTime.now()) |
        f.category((f) => f.color(Colors.white)))
  1. Filter on Reverse References
// All categories that have a todo before today
categories.filter((f) => f.todos((f) => f.dueDate.isBefore(DateTime.now())))

// Aggregates are also supported for the the relation filter:
// Cateogries with more than 5 todos:
categories.filter((f) => f.todos((f) => f.id.count.isBiggerThan(5)))
  1. Nestable
    All filters can go a deeply nested as you want, traveling relations as you wish, with the manager handling all the joins automatically.

  2. Ordering
    You can compose ordering just like filters.
    Although Reverse Filters arent supported :(

// Get the 10 most urgent todos that arent already due
todoEntries
        .orderBy((o) => o.dueDate.asc())
        .filter((f) => f.dueDate.isAfter(DateTime.now()))
        .limit(10)
// You can also use multiple orderings by using `&`
// Sort by date, then alphabetically
todoEntries
        .orderBy((o) => o.dueDate.asc() & o.description.asc())
        .filter((f) => f.dueDate.isAfter(DateTime.now()))
        .limit(10);
  1. Limiting
    Like shown above, you can limit the results of a query by using the limit method.
    You can also use the offset argument to shift the index.
todoEntries
        .orderBy((o) => o.dueDate.asc())
        .limit(10,offset: 5)

For a query will all results, use all

todoEntries.all()

OK, Now what?

Now that we have a built query we can do any of the following CRUD opperations:

  • Delete all matched rows
  • Count all the results
  • Read the results
  • Watch the results
  • Update all the matched items

I have something custom I want to do

No problem, you can add custom filters by adding an extention:

extension on ColumnFilters<int>{
  ComposableFilter moreThan1Billion() => ComposableFilter.simple(column.isBiggerThanValue(1000000000));
}
// Categories with more 1,000,000,000 todos
categories.filter((f) => f.todos((f) => f.id.count.moreThan1Billion()));

Or you can add custom filters or orderings on a table by extending the generated filterset
For example, filter on a combination of rows

class Names extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get firstName => text()();
  TextColumn get lastName => text()();
}

extension on $$NamesTableFilterComposer {
  ColumnFilters<String> get fullName => ColumnFilters(
      $state.table.firstName + Variable(' ') + $state.table.lastName);
}

names.filter((f) => f.fullName('Simon Binder'))

Limitations

What can't this do.

  • Return custom columns
  • Order on aggregates
  • Cure cancer

Postscript

Creating this was tons of fun. My dream is that no-one find this package to scary to use. All these operations are using drift under the hood. use an ORM as a way to learn SQL. Not to avoid it.

@dickermoshe
Copy link
Collaborator Author

dickermoshe commented Apr 3, 2024

@simolus3
I can't use this.
What should I do to have this imported?
Modular is working otherwise!
image

2) Move Distict to positional arg
3) Add limit as a positional arg
4) Fix _tests.dart to _test.dart for CI
5) Move all methods to base - Remove all()
@dickermoshe
Copy link
Collaborator Author

@simolus3
Move some things around, but also realized that the manager tests weren't running.
Was _tests.dart instead of _test.dart.

Didn't make as many changes as I thought I would.
No more changes will be made to the API before release

@dickermoshe
Copy link
Collaborator Author

No more changes will be made to the API before release

I lied, I've added bulkReplace
I've also added basic docs for crud operations, although the filtering docs are going to be the most thorough.

Let me know if should continue

@dickermoshe
Copy link
Collaborator Author

@simolus3
Take a look at the docs that I've added.
I filled your blueprint.
It makes sense to me, but I'm not the target audience. 🤷‍♂️

@dickermoshe
Copy link
Collaborator Author

Btw, this is how server includes related data, it's really nice:
https://docs.serverpod.dev/concepts/database/relation-queries
Their relational queries are also more powerful, with none, count and every filters. This would require aggregations on count which sounds complex.

@dickermoshe
Copy link
Collaborator Author

@simolus3
I was under the impression that this would be included in the 2.17 release 😢
What's the current status?

Copy link
Owner

@simolus3 simolus3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay on this. The documentation looks really great, thank you a lot for the helpful snippets and explanation!

I was under the impression that this would be included in the 2.17 release 😢

Sorry about that. It's been a fairly long time since the 2.16 release, and there have been some important bugfixes that haven't been released yet so I wanted to get them out first.
But for what it's worth, I think we can do a 2.18 release shortly after merging this - this is going to be the most important feature of that release either way, so there's no reason to hold that up.

docs/pages/docs/Dart API/manager.md Show resolved Hide resolved
docs/pages/docs/Dart API/manager.md Outdated Show resolved Hide resolved
Copy link
Owner

@simolus3 simolus3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work, thank you again for this huge contribution!

This looks good to me now, so as long as you don't have anything you still want to change in this PR, I'm happy to merge this.

@dickermoshe
Copy link
Collaborator Author

@simolus3
Yup, I've got nothing else to add to this PR.
Let's Merge!

@simolus3
Copy link
Owner

Wohoo! 🎉 🎉

@simolus3 simolus3 merged commit 231dd63 into simolus3:develop Apr 23, 2024
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants