| 1 | = emDB = |
| 2 | |
| 3 | {{{emDB}}} is a service that provides a database abstraction layer for EdenMobile. |
| 4 | |
| 5 | == Introduction == |
| 6 | |
| 7 | === Tables === |
| 8 | |
| 9 | A '''Table''' object represents a database table, and can be accessed: |
| 10 | |
| 11 | ...directly from the {{{emDB}}} service (asynchronously): |
| 12 | |
| 13 | {{{#!js |
| 14 | emDB.table('tablename').then(function(table) { |
| 15 | ... |
| 16 | }); |
| 17 | }}} |
| 18 | |
| 19 | ...or, where a {{{Resource}}} is available (synchronously): |
| 20 | |
| 21 | {{{#!js |
| 22 | // Access the table of a Resource |
| 23 | var table = resource.table; |
| 24 | |
| 25 | // Access any table via a Resource |
| 26 | var table = resource.getTable('tablename'); |
| 27 | }}} |
| 28 | |
| 29 | === Fields === |
| 30 | |
| 31 | To access a Field in the Table, the Table object provides the {{{$}}} method: |
| 32 | |
| 33 | {{{#!js |
| 34 | var field = table.$('fieldname'); |
| 35 | }}} |
| 36 | |
| 37 | While it is also possible to access a Field like {{{table.fields['fieldname']}}}, this would not map server-side super-IDs (e.g. {{{pe_id}}}) to the EdenMobile {{{em_object_id}}}. Therefore the {{{$}}} method is preferrable, especially in cases where the field name is a variable. |
| 38 | |
| 39 | === Expressions === |
| 40 | |
| 41 | With Tables and Fields, it is possible to construct query '''Expressions''', e.g. |
| 42 | |
| 43 | {{{#!js |
| 44 | var query = field.is(5); |
| 45 | }}} |
| 46 | |
| 47 | AND and OR are functions of query expressions: |
| 48 | {{{#!js |
| 49 | var query = field.is(5).or(field.greaterThan(6)); |
| 50 | }}} |
| 51 | |
| 52 | If there are multiple operand expressions in an AND or OR, the global {{{allOf()}}} and {{{anyOf()}}} functions can used to improve readability: |
| 53 | {{{#!js |
| 54 | // Multiple operand expressions: |
| 55 | var query = field.is(5).or(field.is(6).or(field.is(7))); |
| 56 | |
| 57 | // Alternative with anyOf: |
| 58 | var query = anyOf(field.is(5), field.is(6), field.is(7)); |
| 59 | }}} |
| 60 | |
| 61 | Both {{{anyOf}}} and {{{allOf}}} can of course be nested indefinitely. |
| 62 | |
| 63 | === Sets === |
| 64 | |
| 65 | With a table and a query expression, a '''Set''' can be constructed like: |
| 66 | |
| 67 | {{{#!js |
| 68 | var set = table.where(query); |
| 69 | }}} |
| 70 | |
| 71 | To extract data from a Set, use {{{select()}}}. This is an asynchronous method, so the result comes in a callback: |
| 72 | {{{#!js |
| 73 | set.select([field], function(rows) { |
| 74 | if (rows.len) { |
| 75 | rows.forEach(function(row) { |
| 76 | // Do something with the row |
| 77 | }); |
| 78 | } |
| 79 | }); |
| 80 | }}} |
| 81 | |
| 82 | The result is an array of Rows. |
| 83 | |
| 84 | === Rows === |
| 85 | |
| 86 | The Row object implements a {{{$}}} method again (analogously to accessing fields in a table), but this one accepts both field ''names'' and Fields: |
| 87 | |
| 88 | {{{#!js |
| 89 | // Accessing a column via field name |
| 90 | var value = row.$('fieldname'); |
| 91 | |
| 92 | // Accessing a column with a Field instance |
| 93 | var value = row.$(table.$('fieldname')); |
| 94 | }}} |
| 95 | |
| 96 | Using {{{Field}}}s to access column values is recommended (or even necessary) when selecting from joins. Obviously, you can use a local variable for table.$('fieldname') and use that in both the select() and the subsequent column access. |
| 97 | |
| 98 | === Joins === |
| 99 | |
| 100 | A join can be constructed by calling the {{{join()}}} (=inner join) or {{{left()}}} method of a Table or a Set: |
| 101 | |
| 102 | {{{#!js |
| 103 | table.left( |
| 104 | otherTable.on( |
| 105 | otherTable.$('foreign_key').equals(table.$('id')) |
| 106 | ) |
| 107 | ) |
| 108 | }}} |
| 109 | |
| 110 | ...produces a left join of table with otherTable. The {{{on()}}} method of the Table takes a query expression as argument. |
| 111 | |
| 112 | The {{{join()}}} or {{{left()}}} methods always return the Set (if called from a Table, they will create a new Set), so they can be chained both with more {{{.join()}}} resp {{{.left()}}} (to construct a multiple-table join), or {{{.where()}}} to filter the set, and eventually a {{{.select()}}} to extract data. |
| 113 | |
| 114 | So it can look like this: |
| 115 | {{{#!js |
| 116 | table.left( |
| 117 | otherTable.on(otherTable.$('foreign_key').equals(table.$('id'))) |
| 118 | ).where( |
| 119 | table.$('name').equals('some string') |
| 120 | ).select( |
| 121 | [table.$('name'), otherTable.$('value')], |
| 122 | function(rows) { |
| 123 | ... |
| 124 | }); |
| 125 | }}} |
| 126 | |
| 127 | (written on multiple lines, this becomes very readable - almost undisturbed from the inevitable JS punctuation) |
| 128 | |
| 129 | Is is also possible to chain multiple {{{.where()}}}'s, basically producing an AND of all where() expresions in the chain. Surely, where you construct a Set in place, you wouldn't do multiple .where()'s but rather use allOf(). But if you collect filters (e.g. in a loop), you can easily extend the Set by calling .where() multiple times, i.e. instead of: |
| 130 | |
| 131 | {{{#!js |
| 132 | // Somewhat cumbersome: |
| 133 | for (...) { |
| 134 | ... |
| 135 | if (query) { |
| 136 | query = query.and(subQuery); |
| 137 | } else { |
| 138 | query = subQuery; |
| 139 | } |
| 140 | } |
| 141 | table.where(query).select(...); |
| 142 | }}} |
| 143 | |
| 144 | ...you can do it like: |
| 145 | |
| 146 | {{{#!js |
| 147 | set = table; |
| 148 | for (...) { |
| 149 | ... |
| 150 | set = set.where(subQuery); |
| 151 | } |
| 152 | }}} |
| 153 | |
| 154 | === Orderby and Limitby === |
| 155 | |
| 156 | The {{{select()}}} method accepts an object with arguments as (optional) second parameter, e.g. {{{limitby}}} and {{{orderby}}}: |
| 157 | |
| 158 | {{{#!js |
| 159 | set.select( |
| 160 | [array of fields], |
| 161 | { |
| 162 | limitby: [0,1], |
| 163 | orderby: field |
| 164 | }, |
| 165 | function(rows) {...} |
| 166 | ); |
| 167 | }}} |
| 168 | |
| 169 | NB {{{limitby: [0,1]}}} can also be written as simply {{{limitby: 1}}} |