Thursday, October 31, 2013

ORMapping in Dart (Part2)

I started the implementation of Object Relational Mapping tool like JPA in Dart.
The approach follows the outline I posted before.
But there were several differences.

One is basically I decided to go with minimalist approach.
Always it creates more problem in the end if we rely on many outer resources.
In this case, I just went with my own Postgres scheme generation, sql generation etc.
In fact, it is not so complicated.
And for this kind of tool, we don't need much feature of SQL.

The reason I chose Postgres was the Dart lib for Postgres had BSD license.
MySQL driver in Dart was GPL. But Postgres may be better choice than MySQL technically.

Right now, it can generate DB scheme from annotated class definition, and it support CRUD*create/read(query) /update/delete operations.
persistence object has several level of state.

remaining things are:
  1.  how to support reference and relation. These are actually subtle issue inherent for many ORM system.
    Since if we fetch all associated objects, it may need to copy whole db content.
    So it is important to minimize fetching data.
  2.  how to support relationship. Even though we define relationship as List or reference, but, in particular, the List representation are sort of useless.
    Conceptually, relationship don't have to be represented as entity properties.
    In JPA, it also supports separate query, so it is better not to use List attribute.
 For reference, if we uses strict evaluation, it may end up fetching all data, so we must use lazy evaluation for fetching reference.
There is  some interesting issue for this, since most of postgres API is using Future, how can we avoid asynchronous operation for getting referenced object?
We may need to introduce some operator to change Future to synchronous call by  monitoring the asynchronous call was finished or not.

Also in order to support lazy evaluation, some fieldstate object associated to the filed may need to keep entity id in it, and if the filed is accessed,  it need to fetch from db and assign to the actual object field.
These are a bit complicated, but relatively straightforward.

But the relationship, List field is quite bad.
Essentially if there are large number of associated entities, such whole set is useless. If it cannot be used in general situation, it is better not to use it.

Actually, I created this binding scheme, so I can also change the type to represent relationship.
So instead of List<B>, I may use Rel<A, B>, Ref<A>. and Rel<A, B> class will provide query methods. and it will not maintains fetched objects. In fact, query and managing entity objects for transactional scoped are contradicting requirement.
Often fetched object may not have to be managed(read only).
So it will be useful to provide separate fetching mode so that it will reduce managed fetched objects.

For creating one/many to many relationship, it may be better to reflect such change immediately in database, and do not keep such info in memory unless corresponding object is managed in memory(such case, it need to reflect both sides). this simplify fetching data from database consistently.

class A {
  @OneToMany(mappegBy:"a")
  Rel<A, B> bs;

}
class B {
  @ManyToOne
  Ref<A> a;
}

Following is the test program.

@Table(name: "A")
class A extends Persistence {
    @Id()
    @Basic(optional: false)
    @Column(name: "id")
    String _id;

    @Basic(optional: false)
    @Column(name: "i")
    int _i;
    
    @ManyToOne()
    B _b;

    @OneToMany(mappedBy: "a")
    List<C> _cs;
    
    String get id => get(const Symbol("id"));
    void set id(String v)=>set(const Symbol("id"), v);

    int get i => get(const Symbol("i"));
    void set i(int v)=>set(const Symbol("i"), v);

    B get b => get(const Symbol("b"));
    void set b(B v)=>set(const Symbol("b"), v);

    List<C> get cs => get(const Symbol("cs"));
    void set cs(List<C> v)=>set(const Symbol("cs"), v);
    
    String toString() => "A(id: ${id}, i: ${i}, b: ${b}, cs: ${cs})";
}

@Table(name: "B")
class B extends Persistence {
  @Id()
  @Basic(optional: false)
  @Column(name: "id")
  String _id;

  @Basic(optional: false)
  @Column(name: "s")
  String _s;
  
  @ManyToOne()
  A _a;

  @OneToMany(mappedBy: "a")
  List<C> _cs;
  
  String get id => get(const Symbol("id"));
  void set id(String v)=>set(const Symbol("id"), v);

  String get s => get(const Symbol("s"));
  void set s(String v)=>set(const Symbol("s"), v);

  A get a => get(const Symbol("a"));
  void set a(A v)=>set(const Symbol("a"), v);

  List<C> get cs => get(const Symbol("cs"));
  void set cs(List<C> v)=>set(const Symbol("cs"), v);
  
  String toString() => "B(id: ${id}, s: ${s})";
}

@Table(name: "C")
class C extends Persistence {
  @Id()
  @Basic(optional: false)
  @Column(name: "id")
  String _id;

  @Basic(optional: false)
  @Column(name: "i")
  int _i;
  
  @ManyToOne()
  B _b;

  @OneToMany(mappedBy: "a")
  List<C> _cs;
  
  String get id => get(const Symbol("id"));
  void set id(String v)=>set(const Symbol("id"), v);

  int get i => get(const Symbol("i"));
  void set i(int v)=>set(const Symbol("i"), v);

  B get b => get(const Symbol("b"));
  void set b(B v)=>set(const Symbol("b"), v);

  List<C> get cs => get(const Symbol("cs"));
  void set cs(List<C> v)=>set(const Symbol("cs"), v);
  
  String toString() => "C(id: ${id}, s: ${i})";
}

Future<int> test0(_) =>
    persistenceMgr.deleteTables([A, B, C]).then((int n) {
      print("deleteTables done. ${n}");
    });

Future<int> test1(_) => persistenceMgr.createTables([B, C, A]);

Future<bool> test2(_) {
  A a1 = new A()
    ..managed = true
    ..id = "a1"
    ..i = 10;
  
  A a2 = new A()
    ..managed = true
        ..id = "a2"
        ..i = 20;
 
  return persistenceMgr.commit();
}

Future<bool> test3(_) {
  IClassMirror cmirror = ClassMirrorFactory.reflectClass(A);
  Selector<A> selector = new Selector<A>(cmirror);
  return selector.query(where: "id = 'a1'")
    .then((A a){
      print("==>> a: ${a}");
      a.i = 100;
      return persistenceMgr.commit();
    })
    .then((_)=>selector.queryAll())
    .then((List<A> as){
      print("==>> as: ${as}");
      return true;
    });
}

Future test4a(_) {
  IClassMirror cmirror = ClassMirrorFactory.reflectClass(A);
  Selector<A> selector = new Selector<A>(cmirror);
  return selector.query(where: "id = 'a2'")
    .then((A a){
      print("1a==>> a: ${a}");
      if (a == null) {
        return false;
      }
      print("1b==>> a: ${a}");
      return a.delete();
    })
    .then((ok) {
      if (!ok) {
        return false;
      }
      return selector.query(where: "id = 'a1'")
      .then((A a){
        print("2==>> a: ${a}");
      });    
    });
}

Future test4b(_) {
  IClassMirror cmirror = ClassMirrorFactory.reflectClass(A);
  Selector<A> selector = new Selector<A>(cmirror);
  return selector.queryAll()
  .then((List<A> as)=>Future.forEach(as, (A a)=>a.delete()))
  .then((ok) => selector.query(where: "id = 'a1'"))
  .then((A a){
      print("2==>> a: ${a}");
      //a.delete();
   });
}

void main() {
  
  // register reflection factory
  initClassMirrorFactory();
  
  setDBAdaptor(new PostgresDBAdaptor());
  
  test1(null).then(test2).then(test3).then(test4b).then(test0).then((_) { print(">>> completed."); });
}
And this is the output.

==>createTables: CREATE TABLE B (
id varchar(256) primary key,
s varchar(256),
a varchar(256));

CREATE TABLE C (
id varchar(256) primary key,
i int,
b varchar(256));

CREATE TABLE A (
id varchar(256) primary key,
i int,
b varchar(256));

ALTER TABLE B ADD CONSTRAINT b_a_fkey FOREIGN KEY (a) REFERENCES A(id);
ALTER TABLE C ADD CONSTRAINT c_b_fkey FOREIGN KEY (b) REFERENCES B(id);
ALTER TABLE A ADD CONSTRAINT a_b_fkey FOREIGN KEY (b) REFERENCES B(id);

>> createInsert: INSERT INTO A (id, i, b) VALUES
( E'a1' , 10, null);
>> createInsert: INSERT INTO A (id, i, b) VALUES
( E'a2' , 20, null);
>> DBTable.query: SELECT id, i, b FROM A WHERE id = 'a1'
==>> a: A(id: a1, i: 10, b: null, cs: null)
>> createUpdate: UPDATE A SET id =  E'a1' , i = 10, b = null WHERE id =  E'a1' ;
>> createUpdate: UPDATE A SET id =  E'a2' , i = 20, b = null WHERE id =  E'a2' ;
>> createUpdate: UPDATE A SET id =  E'a1' , i = 100, b = null WHERE id =  E'a1' ;
>> DBTable.queryAll: SELECT id, i, b FROM A
==>> as: [A(id: a2, i: 20, b: null, cs: null), A(id: a1, i: 100, b: null, cs: null)]
>> DBTable.queryAll: SELECT id, i, b FROM A
>> createDelete: DELETE FROM A WHERE id =  E'a2' ;
>> createDelete: DELETE FROM A WHERE id =  E'a1' ;
>> DBTable.query: SELECT id, i, b FROM A WHERE id = 'a1'
2==>> a: null
==>deleteTables: ALTER TABLE A DROP CONSTRAINT a_b_fkey;
ALTER TABLE B DROP CONSTRAINT b_a_fkey;
ALTER TABLE C DROP CONSTRAINT c_b_fkey;
DROP TABLE A, B, C;

deleteTables done. null
>>> completed.

No comments:

Post a Comment