vapor/fluent

๐Ÿ›  Roadmap

tanner0101 opened this issue ยท 18 comments

Let's get a roadmap of what features contributors and users think are important for Fluent 1.1 and Fluent 2.0.

Fluent 1.1

  • Improved Testing
  • Improved Unions
    • Work with modifying and deleting.
    • Pre-load relational models

Fluent 2.0

  • Timestamps
  • Siblings relation needs Left and Right entities (as well as other relations)
  • Allow custom fields on Pivots.
  • Relation attach and detach
  • Fetch subset of data

Comment below with any ideas you have or things you would change or add to this list.

  • UUID support for ID's (or in general any type as an ID)
  • More options for fields (unique, foreign keys ON DELETE actions)
mludi commented

I was already looking into this one, but it doesn't seem easy to provide it properly for all kinds of drivers, but anyway:

  • Support for filtering IN a subquery. (e.g. SELECT * FROM users WHERE username IN (SELECT username FROM some_other_table). One step closer would already be if there is an ability to perform a raw query and return a specificEntity`.
  • Support for transactions, basically allowing multiple queries to be performed in a single transaction and possibly rolled back.
  • Date support.
  • Data support (this might be an addition for Node instead, allowing to use the Foundation Data struct)

These are all the base types that Active Record supports. It would be nice if they could all be handled by fluent. Given that Active Record is also DB agnostic at this layer, it seems that it would be possible to implement all of them in fluent as well.

  • Boolean
  • String (char)
  • Binary
  • Text
  • Date
  • Time
  • DateTime
  • Float
  • Integer

Additionally these types are aliased:

  • blob => "binary"
  • clob => "text"
  • timestamp => "datetime"
  • numeric => "decimal"
  • number => "decimal"
  • double => "float"

https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L496-L512

Personally, the absence of support for any type of datetime in fluent is a major road block to the adoption of Vapor.

I am wondering with the support for more types in mind (e.g. UUID, Date, Time), how would this be implemented without changing Node, which I can imagine should not really be changed. It would not be incredibly performant to have to convert between different types all the time. It would be nicer to be able to use Date and UUID directly.

@sroebert that is correct about changing Node.

We should ideally focus on features that can be achieved w/o too much modification. It is meant to be extremely agnostic, so each addition should be approached very carefully, but we should also be open to the idea in more forward looking ideas as we eye 2.0.

Bulk modifications using syntax update(field: String, value: Node)

User.all().update(field: "active", value: false)
bygri commented

This may already be implementedโ€”if so, I can't find itโ€”but the ability to iterate over large result sets without loading the whole set of model instances into memory as per all(). In other words, keeping hold of the query cursor so a series of fetchNext() calls can be made.

Support for RAND()

Improved error messages. Errors like Fluent.RelationError error 0 don't provide much guidance. Fixed with vapor/vapor#823

Fluent:

public static var name: String {
        return String(describing: self).lowercased()
    }

instead of the name I gave the api:
I originally used database.create("commentaries")

extension Commentary: Preparation {
    static func prepare(_ database: Database) throws {
        try database.create("commentarys") { document in  //3hrs debug to find MySQL need 's' added to commentary not commentaries
            document.id()

apparently a common Rails trap too.

I found the answer.
static var entity = "new_name"

My suggestion now is to remove the default implementation extension for entity part of the Entity protocol and add this to the boilerplate.

extension Commentary: Preparation {
    static func prepare(_ database: Database) throws {
        try database.create(self.entity) { document in  
            document.id()

this would be a breaking change though so there should be a deprecation console log placed in the default implementation first I suppose.

I think something that would be very useful is lazily loading resulting models.
Right now queries on large numbers of models are quite expensive both in CPU and memory but lazily loading models by leveraging an intermediary class would drastically improve the performance.

This is a functionality that is found in ORMs such as Doctrine and Active Record.

Specifically for the lazy loading, what needs to be looked at is line 69 in Query.swift:

let model = try T(node: result, in: _context)

I think it's very common for web applications to perform queries on thousands of items, and often the models only need to be instantiated when iterated over.

I need to play around with a possible intermediary class that represents the uninstantiated entity to see if it would help. However, the downsides of lazy loading are the added overhead of storing the raw query results somewhere and the hassle of dealing with entities that are possibly loaded or possibly not...

I'm curious if the rest of the community is interested in this?

bygri commented

My attempted solution was to still instantiate the model directly from the query, but only iteratively, so only one model object is loaded at a time. I think it's a fair middle ground. I could not myself think of any situation where this solution would not fit - filtering, offsets, limiting are generally better done on the database end, and could still done iteratively if really necessary.

Closing this as we are nearing 2.0s release. Please feel free to open new issues for any features you'd like to see in the coming versions. Thanks!