UNPKG

25.7 kBMarkdownView Raw
1## Object Relational Mapping for fibjs
2
3[![NPM version](https://img.shields.io/npm/v/@fxjs/orm.svg)](https://www.npmjs.org/package/@fxjs/orm)
4[![Build Status](https://travis-ci.org/fxjs-modules/orm.svg)](https://travis-ci.org/fxjs-modules/orm)
5[![Build status](https://ci.appveyor.com/api/projects/status/gjqno52nm2ff2a92?svg=true)](https://ci.appveyor.com/project/richardo2016/orm)
6
7## Install
8
9```sh
10npm install @fxjs/orm
11```
12
13## Test
14
15```sh
16npm run ci
17```
18
19## DBMS Support
20
21- MySQL & MariaDB
22- SQLite
23
24## Features
25
26## Introduction
27
28This is a fibjs object relational mapping module.
29
30An example:
31
32```js
33var orm = require("@fxjs/orm");
34
35var db = orm.connectSync("mysql://username:password@host/database");
36var Person = db.define("person", {
37 name : String,
38 surname : String,
39 age : Number, // FLOAT
40 male : Boolean,
41 continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type
42 photo : Buffer, // BLOB/BINARY
43 data : Object // JSON encoded
44}, {
45 methods: {
46 fullName: function () {
47 return this.name + ' ' + this.surname;
48 }
49 },
50 validations: {
51 age: orm.enforce.ranges.number(18, undefined, "under-age")
52 }
53});
54
55// add the table to the database
56db.syncSync();
57
58// add a row to the person table
59Person.createSync({ id: 1, name: "John", surname: "Doe", age: 27 });
60
61// query the person table by surname
62var people = Person.findSync({ surname: "Doe" });
63// SQL: "SELECT * FROM person WHERE surname = 'Doe'"
64
65console.log("People found: %d", people.length);
66console.log("First person: %s, age %d", people[0].fullName(), people[0].age);
67
68people[0].age = 16;
69people[0].saveSync();
70```
71
72The callback version like this:
73```js
74var orm = require("orm");
75
76orm.connect("mysql://username:password@host/database", function (err, db) {
77 if (err) throw err;
78
79 var Person = db.define("person", {
80 name : String,
81 surname : String,
82 age : Number, // FLOAT
83 male : Boolean,
84 continent : [ "Europe", "America", "Asia", "Africa", "Australia", "Antartica" ], // ENUM type
85 photo : Buffer, // BLOB/BINARY
86 data : Object // JSON encoded
87 }, {
88 methods: {
89 fullName: function () {
90 return this.name + ' ' + this.surname;
91 }
92 },
93 validations: {
94 age: orm.enforce.ranges.number(18, undefined, "under-age")
95 }
96 });
97
98 // add the table to the database
99 db.sync(function(err) {
100 if (err) throw err;
101
102 // add a row to the person table
103 Person.create({ id: 1, name: "John", surname: "Doe", age: 27 }, function(err) {
104 if (err) throw err;
105
106 // query the person table by surname
107 Person.find({ surname: "Doe" }, function (err, people) {
108 // SQL: "SELECT * FROM person WHERE surname = 'Doe'"
109 if (err) throw err;
110
111 console.log("People found: %d", people.length);
112 console.log("First person: %s, age %d", people[0].fullName(), people[0].age);
113
114 people[0].age = 16;
115 people[0].save(function (err) {
116 // err.msg = "under-age";
117 });
118 });
119 });
120 });
121});
122```
123
124## Documentation
125
126
127Fibjs did not add new functions, the development of documents can refer to node-orm, only need to change the asynchronous call to synchronous version. [wiki](https://github.com/fxjs-modules/orm/wiki/).
128
129## Settings
130
131See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Settings).
132
133## Connecting
134
135See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Connecting-to-Database).
136
137## Models
138
139A Model is an abstraction over one or more database tables. Models support associations (more below). The name of the model is assumed to match the table name.
140
141Models support behaviours for accessing and manipulating table data.
142
143## Defining Models
144
145See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Defining-Models).
146
147### Properties
148
149See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Model-Properties).
150
151### Instance Methods
152
153Are passed in during model definition.
154
155```js
156var Person = db.define('person', {
157 name : String,
158 surname : String
159}, {
160 methods: {
161 fullName: function () {
162 return this.name + ' ' + this.surname;
163 }
164 }
165});
166
167var person = Person.getSync(4);
168console.log( person.fullName() );
169```
170
171### Model Methods
172
173Are defined directly on the model.
174
175```js
176var Person = db.define('person', {
177 name : String,
178 height : { type: 'integer' }
179});
180Person.tallerThan = function(height) {
181 return this.findSync({ height: orm.gt(height) });
182};
183
184var tallPeople = Person.tallerThan( 192);
185```
186
187<!--
188## Loading Models [NOT SUPPORT]
189
190Models can be in separate modules. Simply ensure that the module holding the models uses module.exports to publish a function that accepts the database connection, then load your models however you like.
191
192Note - using this technique you can have cascading loads.
193
194```js
195// your main file (after connecting)
196db.loadSync("./models");
197// loaded!
198var Person = db.models.person;
199var Pet = db.models.pet;
200
201// models.js
202module.exports = function (db) {
203 db.loadSync("./models-extra");
204 db.define('person', {
205 name : String
206 });
207};
208
209// models-extra.js
210module.exports = function (db) {
211 db.define('pet', {
212 name : String
213 });
214};
215``` -->
216
217## Synchronizing Models
218
219See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Syncing-and-dropping-models).
220
221## Dropping Models
222
223See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Syncing-and-dropping-models).
224
225## Advanced Options
226
227ORM2 allows you some advanced tweaks on your Model definitions. You can configure these via settings or in the call to `define` when you setup the Model.
228
229For example, each Model instance has a unique ID in the database. This table column is added automatically, and called "id" by default.<br/>
230If you define your own `key: true` column, "id" will not be added:
231
232```js
233var Person = db.define("person", {
234 personId : { type: 'serial', key: true },
235 name : String
236});
237
238// You can also change the default "id" property name globally:
239db.settings.set("properties.primary_key", "UID");
240
241// ..and then define your Models
242var Pet = db.define("pet", {
243 name : String
244});
245```
246
247**Pet** model will have 2 columns, an `UID` and a `name`.
248
249It's also possible to have composite keys:
250
251```js
252var Person = db.define("person", {
253 firstname : { type: 'text', key: true },
254 lastname : { type: 'text', key: true }
255});
256```
257
258Other options:
259
260- `identityCache` : (default: `false`) Set it to `true` to enable identity cache ([Singletons](#singleton)) or set a timeout value (in seconds);
261- `autoSave` : (default: `false`) Set it to `true` to save an Instance right after changing any property;
262- `autoFetch` : (default: `false`) Set it to `true` to fetch associations when fetching an instance from the database;
263- `autoFetchLimit` : (default: `1`) If `autoFetch` is enabled this defines how many hoops (associations of associations)
264 you want it to automatically fetch.
265
266## Hooks
267
268See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Model-Hooks).
269
270## Finding Items
271
272### Model.getSync(id, [ options ])
273
274To get a specific element from the database use `Model.get`.
275
276```js
277var person = Person.getSync(123);
278// finds person with id = 123
279```
280
281### Model.findSync([ conditions ] [, options ] [, limit ] [, order ])
282
283Finding one or more elements has more options, each one can be given in no specific parameter order. Only `options` has to be after `conditions` (even if it's an empty object).
284
285```js
286var people = Person.findSync({ name: "John", surname: "Doe" }, 3);
287// finds people with name='John' AND surname='Doe' and returns the first 3
288```
289
290If you need to sort the results because you're limiting or just because you want them sorted do:
291
292```js
293var people = Person.findSync({ surname: "Doe" }, "name");
294// finds people with surname='Doe' and returns sorted by name ascending
295
296people = Person.findSync({ surname: "Doe" }, [ "name", "Z" ]);
297// finds people with surname='Doe' and returns sorted by name descending
298// ('Z' means DESC; 'A' means ASC - default)
299```
300
301There are more options that you can pass to find something. These options are passed in a second object:
302
303```js
304var people = Person.findSync({ surname: "Doe" }, { offset: 2 });
305// finds people with surname='Doe', skips the first 2 and returns the others
306```
307
308You can also use raw SQL when searching. It's documented in the *Chaining* section below.
309
310### Model.countSync([ conditions ])
311
312If you just want to count the number of items that match a condition you can just use `.count()` instead of finding all
313of them and counting. This will actually tell the database server to do a count (it won't be done in the node process itself).
314
315```js
316var count = Person.countSync({ surname: "Doe" });
317console.log("We have %d Does in our db", count);
318```
319
320### Model.existsSync([ conditions ])
321
322Similar to `.count()`, this method just checks if the count is greater than zero or not.
323
324```js
325var exists = Person.existsSync({ surname: "Doe" });
326console.log("We %s Does in our db", exists ? "have" : "don't have");
327```
328
329### Aggregating Functions
330
331
332An `Array` of properties can be passed to select only a few properties. An `Object` is also accepted to define conditions.
333
334Here's an example to illustrate how to use `.groupBy()`:
335
336```js
337//The same as "select avg(weight), age from person where country='someCountry' group by age;"
338var stats = Person.aggregate(["age"], { country: "someCountry" }).avg("weight").groupBy("age").getSync();
339// stats is an Array, each item should have 'age' and 'avg_weight'
340```
341
342### Base `.aggregate()` methods
343
344- `.limit()`: you can pass a number as a limit, or two numbers as offset and limit respectively
345- `.order()`: same as `Model.find().order()`
346
347### Additional `.aggregate()` methods
348
349- `min`
350- `max`
351- `avg`
352- `sum`
353- `count` (there's a shortcut to this - `Model.count`)
354
355There are more aggregate functions depending on the driver (Math functions for example).
356
357### Chaining
358
359If you prefer less complicated syntax you can chain `.find()` by not giving a callback parameter.
360
361```js
362var people = Person.find({ surname: "Doe" }).limit(3).offset(2).only("name", "surname").runSync();
363// finds people with surname='Doe', skips first 2 and limits to 3 elements,
364// returning only 'name' and 'surname' properties
365```
366If you want to skip just one or two properties, you can call `.omit()` instead of `.only`.
367
368Chaining allows for more complicated queries. For example, we can search by specifying custom SQL:
369```js
370Person.find({ age: 18 }).where("LOWER(surname) LIKE ?", ['dea%']).allSync( ... );
371```
372It's bad practice to manually escape SQL parameters as it's error prone and exposes your application to SQL injection.
373The `?` syntax takes care of escaping for you, by safely substituting the question mark in the query with the parameters provided.
374You can also chain multiple `where` clauses as needed.
375
376`.find`, `.where` & `.all` do the same thing; they are all interchangeable and chainable.
377
378You can also `order` or `orderRaw`:
379```js
380Person.find({ age: 18 }).order('-name').allSync( ... );
381// see the 'Raw queries' section below for more details
382Person.find({ age: 18 }).orderRaw("?? DESC", ['age']).allSync( ... );
383```
384
385You can also chain and just get the count in the end. In this case, offset, limit and order are ignored.
386
387```js
388var people = Person.find({ surname: "Doe" }).countSync();
389// people = number of people with surname="Doe"
390```
391
392Also available is the option to remove the selected items.
393Note that a chained remove will not run any hooks.
394
395```js
396Person.find({ surname: "Doe" }).removeSync();
397// Does gone..
398```
399
400You can also make modifications to your instances using common Array traversal methods and save everything in the end. [NOT SUPPORT]
401
402```js
403Person.find({ surname: "Doe" }).each(function (person) {
404 person.surname = "Dean";
405}).save(function (err) {
406 // done!
407});
408
409Person.find({ surname: "Doe" }).each().filter(function (person) {
410 return person.age >= 18;
411}).sort(function (person1, person2) {
412 return person1.age < person2.age;
413}).get(function (people) {
414 // get all people with at least 18 years, sorted by age
415});
416```
417
418Of course you could do this directly on `.find()`, but for some more complicated tasks this can be very usefull.
419
420`Model.find()` does not return an Array so you can't just chain directly. To start chaining you have to call
421`.each()` (with an optional callback if you want to traverse the list). You can then use the common functions
422`.filter()`, `.sort()` and `.forEach()` more than once.
423
424In the end (or during the process..) you can call:
425- `.countSync()` if you just want to know how many items there are;
426- `.getSync()` to retrieve the list;
427- `.saveSync()` to save all item changes.
428
429#### Conditions
430
431Conditions are defined as an object where every key is a property (table column). All keys are supposed
432to be concatenated by the logical `AND`. Values are considered to match exactly, unless you're passing
433an `Array`. In this case it is considered a list to compare the property with.
434
435```js
436{ col1: 123, col2: "foo" } // `col1` = 123 AND `col2` = 'foo'
437{ col1: [ 1, 3, 5 ] } // `col1` IN (1, 3, 5)
438```
439
440If you need other comparisons, you have to use a special object created by some helper functions. Here are
441a few examples to describe it:
442
443```js
444{ col1: orm.eq(123) } // `col1` = 123 (default)
445{ col1: orm.ne(123) } // `col1` <> 123
446{ col1: orm.gt(123) } // `col1` > 123
447{ col1: orm.gte(123) } // `col1` >= 123
448{ col1: orm.lt(123) } // `col1` < 123
449{ col1: orm.lte(123) } // `col1` <= 123
450{ col1: orm.between(123, 456) } // `col1` BETWEEN 123 AND 456
451{ col1: orm.not_between(123, 456) } // `col1` NOT BETWEEN 123 AND 456
452{ col1: orm.like(12 + "%") } // `col1` LIKE '12%'
453{ col1: orm.not_like(12 + "%") } // `col1` NOT LIKE '12%'
454{ col1: orm.not_in([1, 4, 8]) } // `col1` NOT IN (1, 4, 8)
455```
456
457#### Raw queries
458
459```js
460var data = db.driver.execQuerySync("SELECT id, email FROM user")
461
462// You can escape identifiers and values.
463// For identifier substitution use: ??
464// For value substitution use: ?
465var data = db.driver.execQuerySync(
466 "SELECT user.??, user.?? FROM user WHERE user.?? LIKE ? AND user.?? > ?",
467 ['id', 'name', 'name', 'john', 'id', 55])
468
469// Identifiers don't need to be scaped most of the time
470var data = db.driver.execQuerySync(
471 "SELECT user.id, user.name FROM user WHERE user.name LIKE ? AND user.id > ?",
472 ['john', 55])
473```
474
475### Identity pattern
476
477You can use the identity pattern (turned off by default). If enabled, multiple different queries will result in the same result - you will
478get the same object. If you have other systems that can change your database or you need to call some manual SQL queries,
479you shouldn't use this feature. It is also know to cause some problems with complex
480autofetch relationships. Use at your own risk.
481
482It can be enabled/disabled per model:
483
484```js
485var Person = db.define('person', {
486 name : String
487}, {
488 identityCache : true
489});
490```
491
492and also globally:
493
494```js
495var db = orm.connectSync('...');
496db.settings.set('instance.identityCache', true);
497```
498
499The identity cache can be configured to expire after a period of time by passing in a number instead of a
500boolean. The number will be considered the cache timeout in seconds (you can use floating point).
501
502**Note**: One exception about Caching is that it won't be used if an instance is not saved. For example, if
503you fetch a Person and then change it, while it doesn't get saved it won't be passed from Cache.
504
505## Creating Items
506
507### Model.createSync(items)
508
509To insert new elements to the database use `Model.create`.
510
511```js
512var items = Person.createSync([
513 {
514 name: "John",
515 surname: "Doe",
516 age: 25,
517 male: true
518 },
519 {
520 name: "Liza",
521 surname: "Kollan",
522 age: 19,
523 male: false
524 }
525]);
526// items - array of inserted items
527```
528
529## Updating Items
530
531Every item returned has the properties that were defined to the Model and also a couple of methods you can
532use to change each item.
533
534```js
535var John = Person.getSync(1);
536John.name = "Joe";
537John.surname = "Doe";
538John.saveSync();
539console.log("saved!");
540```
541
542Updating and then saving an instance can be done in a single call:
543
544```js
545var John = Person.getSync(1);
546John.saveSync({ name: "Joe", surname: "Doe" });
547console.log("saved!");
548```
549
550If you want to remove an instance, just do:
551
552```js
553// you could do this without even fetching it, look at Chaining section above
554var John = Person.getSync(1);
555John.removeSync();
556console.log("removed!");
557```
558
559## Validations
560
561See information in the [wiki](https://github.com/fxjs-modules/orm/wiki/Model-Validations).
562
563## Associations
564
565An association is a relation between one or more tables.
566
567### hasOne
568
569Is a **many to one** relationship. It's the same as **belongs to.**<br/>
570Eg: `Animal.hasOne('owner', Person)`.<br/>
571Animal can only have one owner, but Person can have many animals.<br/>
572Animal will have the `owner_id` property automatically added.
573
574The following functions will become available:
575```js
576animal.getOwnerSync() // Gets owner
577animal.setOwnerSync(person) // Sets owner_id
578animal.hasOwnerSync() // Checks if owner exists
579animal.removeOwnerSync() // Sets owner_id to 0
580```
581
582**Chain Find**
583
584The hasOne association is also chain find compatible. Using the example above, we can do this to access a new instance of a ChainFind object:
585
586```js
587Animal.findByOwner({ /* options */ })
588```
589
590**Reverse access**
591
592```js
593Animal.hasOne('owner', Person, {reverse: 'pets'})
594```
595
596will add the following:
597
598```js
599// Instance methods
600person.getPetsSync(function..)
601person.setPetsSync(cat, function..)
602
603// Model methods
604Person.findByPets({ /* options */ }) // returns ChainFind object
605```
606
607### hasMany
608
609Is a **many to many** relationship (includes join table).<br/>
610Eg: `Patient.hasMany('doctors', Doctor, { why: String }, { reverse: 'patients', key: true })`.<br/>
611Patient can have many different doctors. Each doctor can have many different patients.
612
613This will create a join table `patient_doctors` when you call `Patient.sync()`:
614
615 column name | type
616 :-----------|:--------
617 patient_id | Integer (composite key)
618 doctor_id | Integer (composite key)
619 why | varchar(255)
620
621The following functions will be available:
622
623```js
624patient.getDoctorsSync() // List of doctors
625patient.addDoctorsSync(docs) // Adds entries to join table
626patient.setDoctorsSync(docs) // Removes existing entries in join table, adds new ones
627patient.hasDoctorsSync(docs) // Checks if patient is associated to specified doctors
628patient.removeDoctorsSync(docs) // Removes specified doctors from join table
629
630doctor.getPatientsSync()
631// etc...
632
633// You can also do:
634patient.doctors = [doc1, doc2];
635patient.saveSync()
636
637// Model methods
638Patient.findByDoctorsSync({ /* conditions */ }) // Find patients by conditions for doctors
639```
640
641To associate a doctor to a patient:
642
643```js
644patient.addDoctorSync(surgeon, {why: "remove appendix"})
645```
646
647which will add `{patient_id: 4, doctor_id: 6, why: "remove appendix"}` to the join table.
648
649#### getAccessor
650
651This accessor in this type of association returns a `ChainFind` if not passing a callback. This means you can do things like:
652
653```js
654var doctors = patient.getDoctors().order("name").offset(1).runSync());
655// ... all doctors, ordered by name, excluding first one
656```
657
658### extendsTo
659
660If you want to split maybe optional properties into different tables or collections. Every extension will be in a new table,
661where the unique identifier of each row is the main model instance id. For example:
662
663```js
664var Person = db.define("person", {
665 name : String
666});
667var PersonAddress = Person.extendsTo("address", {
668 street : String,
669 number : Number
670});
671```
672
673This will create a table `person` with columns `id` and `name`. The extension will create a table `person_address` with
674columns `person_id`, `street` and `number`. The methods available in the `Person` model are similar to an `hasOne`
675association. In this example you would be able to call `.getAddress(cb)`, `.setAddress(Address, cb)`, ..
676
677**Note:** you don't have to save the result from `Person.extendsTo`. It returns an extended model. You can use it to query
678directly this extended table (and even find the related model) but that's up to you. If you only want to access it using the
679original model you can just discard the return.
680
681### Examples & options
682
683If you have a relation of 1 to n, you should use `hasOne` (belongs to) association.
684
685```js
686var Person = db.define('person', {
687 name : String
688});
689var Animal = db.define('animal', {
690 name : String
691});
692Animal.hasOne("owner", Person); // creates column 'owner_id' in 'animal' table
693
694// get animal with id = 123
695var animal = Animal.getSync(123);
696// animal is the animal model instance, if found
697var person = animal.getOwnerSync();
698// if animal has really an owner, person points to it
699```
700
701You can mark the `owner_id` field as required in the database by specifying the `required` option:
702```js
703Animal.hasOne("owner", Person, { required: true });
704```
705
706If a field is not required, but should be validated even if it is not present, then specify the `alwaysValidate` option.
707(this can happen, for example when validation of a null field depends on other fields in the record)
708```js
709Animal.hasOne("owner", Person, { required: false, alwaysValidate: true });
710```
711
712If you prefer to use another name for the field (owner_id) you can change this parameter in the settings.
713
714```js
715db.settings.set("properties.association_key", "{field}_{name}"); // {name} will be replaced by 'owner' and {field} will be replaced by 'id' in this case
716```
717
718**Note: This has to be done before the association is specified.**
719
720The `hasMany` associations can have additional properties in the association table.
721
722```js
723var Person = db.define('person', {
724 name : String
725});
726Person.hasMany("friends", {
727 rate : Number
728}, {}, { key: true });
729
730var John = Person.getSync(123);
731var friends = John.getFriendsSync();
732// assumes rate is another column on table person_friends
733// you can access it by going to friends[N].extra.rate
734```
735
736If you prefer you can activate `autoFetch`.
737This way associations are automatically fetched when you get or find instances of a model.
738
739```js
740var Person = db.define('person', {
741 name : String
742});
743Person.hasMany("friends", {
744 rate : Number
745}, {
746 key : true, // Turns the foreign keys in the join table into a composite key
747 autoFetch : true
748});
749
750var John = Person.getSync(123);
751// no need to do John.getFriends() , John already has John.friends Array
752```
753
754You can also define this option globally instead of a per association basis.
755
756```js
757var Person = db.define('person', {
758 name : String
759}, {
760 autoFetch : true
761});
762Person.hasMany("friends", {
763 rate : Number
764}, {
765 key: true
766});
767```
768
769Associations can make calls to the associated Model by using the `reverse` option. For example, if you have an
770association from ModelA to ModelB, you can create an accessor in ModelB to get instances from ModelA.
771Confusing? Look at the next example.
772
773```js
774var Pet = db.define('pet', {
775 name : String
776});
777var Person = db.define('person', {
778 name : String
779});
780Pet.hasOne("owner", Person, {
781 reverse : "pets"
782});
783
784var pets = Person(4).getPetsSync();
785// although the association was made on Pet,
786// Person will have an accessor (getPets)
787//
788// In this example, ORM will fetch all pets
789// whose owner_id = 4
790
791Pet.findByOwnersSync({ /* conditions */ }) // Find pets by conditions for their owners
792```
793
794This makes even more sense when having `hasMany` associations since you can manage the *many to many*
795associations from both sides.
796
797```js
798var Pet = db.define('pet', {
799 name : String
800});
801var Person = db.define('person', {
802 name : String
803});
804Person.hasMany("pets", Pet, {
805 bought : Date
806}, {
807 key : true,
808 reverse : "owners"
809});
810
811Person(1).getPetsSync(...);
812Pet(2).getOwnersSync(...);
813
814Person.findByPetsSync({ /* conditions */ }) // Find people by conditions for their pets
815Pet.findByOwnersSync({ /* conditions */ }) // Find pets by conditions for their owners
816```
817
818## Accessors
819
820Like examples above, there are accessors in every **Associations**, all types of accessors are listed below,in this case, `Person.hasMany('pets', Pet, {}, {reverse: 'owner'})`
821
822 Accessor Type | Callor | hasOne | hasMany | extendsTo | Sample
823 :-----------|:--------|:--------|:--------|:--------|:--------
824 hasAccessor | instance | ✔️ | ✔️ | ✔️ | `pet.hasOwnersSync()`
825 getAccessor | instance | ✔️ | ✔️ | ✔️ | `pet.getOwnersSync()`
826 setAccessor | instance | ✔️ | ✔️ | ✔️ | `pet.setOwnersSync()`
827 delAccessor | instance | ✔️ | ✔️ | ✔️ | `person.removePetsSync([pet])`; `person.removePetsSync()`
828 addAccessor | instance | ✔️ | ❎ | ❎ | `person.addPetsSync([pet])`
829 modelFindByAccessor | model | ✔️ | ✔️ | ✔️ | `Pet.findByOwnersSync({name: "Butt"})`
830
831
832Those accessors makes sense for all associations (`hasOne`, `hasMany`, `extendsTo`), view details in [test cases](./test/integration).
833
834## Transaction support
835
836You can use low level transaction function to process db transcation.
837```js
838db.begin();
839...
840if(err)
841 db.rollback();
842else
843 db.commit();
844```
845Or you can use trans to simpile process it.
846```js
847var result = db.trans(() => {
848 ...
849 return result;
850});
851```
852
853## Adding external database adapters
854
855To add an external database adapter to `orm`, call the `addAdapter` method, passing in the alias to use for connecting
856with this adapter, along with the constructor for the adapter:
857
858```js
859require('orm').addAdapter('cassandra', CassandraAdapter);
860```
861
862See [the documentation for creating adapters](./Adapters.md) for more details.