ActiveRecordUserGuide.page 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. POCO ActiveRecord User Guide
  2. POCO ActiveRecord Framework
  3. !!!Introduction
  4. POCO ActiveRecord is a simple and lightweight object-relational mapping (ORM) framework
  5. built on top of the POCO Data library. The main goal of POCO ActiveRecord is
  6. to relieve developers from having to write lots of boilerplate database
  7. query code for common operations like finding an object by ID, updating an object, deleting
  8. an object or running paged queries. As its name implies, the framework follows
  9. the well-known [[https://en.wikipedia.org/wiki/Active_record_pattern Active Record]]
  10. architectural pattern. It's based on a code generator (named <*ActiveRecord Compiler*>,
  11. or <[arc]>) and uses a convention-over-configuration approach.
  12. !!!Getting Started
  13. The starting point for using the ActiveRecord framework is an XML file.
  14. The XML file describes the classes that correspond to database tables,
  15. and their relationships. From that XML file, the ActiveRecord Compiler
  16. generates corresponding header and source files defining and implementing
  17. the respective C++ classes, as well as type handlers for the POCO Data
  18. library.
  19. Following is an example for such an XML file. The file defines two
  20. classes, an `Employee` class (mapped to a table named `employees`), and
  21. a `Role` class (mapped to a table named `roles`).
  22. <project namespace="Sample">
  23. <class name="Employee" table="employees">
  24. <property name="id" type="uuid"/>
  25. <property name="name" type="string"/>
  26. <property name="ssn" type="string"/>
  27. <property name="role" type="int16" references="Role"/>
  28. <property name="manager" type="uuid" references="Employee" cardinality="?"/>
  29. </class>
  30. <class name="Role" table="roles" autoIncrementID="true">
  31. <property name="id" type="int16"/>
  32. <property name="name" type="string"/>
  33. <property name="description" type="string"/>
  34. </class>
  35. </project>
  36. ----
  37. There is a n:1 relationship between `Employee` and `Role` (each employee
  38. has exactly one role). Furthermore, each employee can optionally have
  39. a manager, which is again an `Employee`.
  40. Properties named `id` are considered to be primary keys, unless a different
  41. property has been designated the primary key (with the `key` attribute in
  42. the `class` element). It's also possible to have objects without a primary key
  43. or ID column (keyless active records).
  44. The generated C++ classes will be in the `Sample` namespace, as specified
  45. in the <[project]> element.
  46. The definitions in the XML file correspond to the database schema built
  47. by the following <[CREATE TABLE]> statements:
  48. CREATE TABLE employees (
  49. id CHAR(36) PRIMARY KEY,
  50. name VARCHAR(64),
  51. ssn VARCHAR(32),
  52. role INTEGER,
  53. manager CHAR(36)
  54. );
  55. CREATE TABLE roles (
  56. id INTEGER PRIMARY KEY AUTOINCREMENT,
  57. name VARCHAR(64),
  58. description VARCHAR(256)
  59. );
  60. ----
  61. If the database engine supports it, the `id` column of the `employees` table can be
  62. an UUID as well.
  63. Running the ActiveRecord Compiler with the above XML file (sample.xml) with the
  64. following statement:
  65. $ arc sample.xml
  66. ----
  67. will create the following files in the current working directory:
  68. include/
  69. Sample/
  70. Employee.h
  71. Role.h
  72. src/
  73. Employee.cpp
  74. Role.cpp
  75. ----
  76. The generated classes are derived from the Poco::ActiveRecord::ActiveRecord class
  77. template and have accessor methods for all properties defined in the XML file,
  78. as well as methods for creating, updating and deleting instances in the database.
  79. ActiveRecord objects are reference counted, and every generated class contains
  80. a `Ptr` type alias for an appropriate Poco::AutoPtr<>.
  81. !!The Context
  82. ActiveRecord uses a Context (Poco::ActiveRecord::Context) class to bind objects to
  83. a database session (Poco::Data::Session). In addition to the database session,
  84. the Context also holds a connector-specific
  85. Poco::ActiveRecord::StatementPlaceholderProvider. This class makes sure generated
  86. SQL statements have the correct placeholders for the respective database backend.
  87. For most database backends, the `?` placeholders will be fine, but PostgreSQL
  88. has a different placeholder format (`$1`, `$2`, etc). The Context's StatementPlaceholderProvider
  89. takes care of that.
  90. Every ActiveRecord object must be associated with a Context, before any database
  91. operations can take place. Context objects are relatively lightweight, so they
  92. can be created whenever needed. Context objects are reference-counted, so a Context
  93. object will be kept alive as long as at least one ActiveRecord object still references it.
  94. !!Creating an Object
  95. The following code snippet shows how to create a new `Role` object and insert it into
  96. the `roles` table.
  97. Poco::Data::Session session("SQLite", "data.sqlite");
  98. Context::Ptr pContext = new Context(session);
  99. Role::Ptr pDeveloper = new Role;
  100. pDeveloper->name("Developer")
  101. .description("Developer role");
  102. pDeveloper->create(pContext);
  103. ----
  104. As can be seen, setters (`name()`, `description()` in this case) can be chained.
  105. The `create()` method will bind the object to a Context and then execute
  106. an `INSERT` statement to insert the object into the `roles` table.
  107. !!Finding an Object
  108. The following code snippet shows how to find a `Role` object by its ID (1).
  109. Poco::Data::Session session("SQLite", "data.sqlite");
  110. Context::Ptr pContext = new Context(session);
  111. Role::Ptr pRole = Role::find(pContext, 1);
  112. std::cout
  113. << "name: " << pRole->name() << "\n"
  114. << "description: " << pRole->description() << std::endl;
  115. ----
  116. !!Updating an Object
  117. Updating an object involves first updating the respective properties using
  118. the setter functions, then calling the `update()` method. To update an
  119. ActiveRecord object, the object must already be bound to a Context.
  120. Objects returned from `find()`, or from a query will already be bound to a Context.
  121. Note that the following snippets will omit the session and context setup code.
  122. Role::Ptr pRole = Role::find(pContext, 1);
  123. pRole->description("New developer role");
  124. pRole->update();
  125. ----
  126. !!Deleting an Object
  127. An object bound to a Context can be deleted by calling the `remove()` method.
  128. Role::Ptr pRole = Role::find(pContext, 1);
  129. pRole->remove();
  130. ----
  131. !!Queries
  132. Finding objects by their IDs alone is fine if the respective IDs are already known.
  133. However, in most cases, ActiveRecord objects will be obtained by executing
  134. a query. To do that, the ActiveRecord framework provides the
  135. Poco::ActiveRecord::Query class template. The Query template must be instantiated
  136. with the class of the resulting objects. The Query class will generate a
  137. `SELECT` statement. Query parameters can be specified via data binding. The
  138. `?` placeholder can be used regardless of the underlying database backend. The
  139. Query class will replace it with the appropriate placeholder for the backend.
  140. Actual query parameters are bound with the `bind()` method. The query is then
  141. executed by calling the `execute()` method.
  142. The result of a Query is a `std::vector` containing pointers (Poco::AutoPtr)
  143. to returned objects.
  144. Poco::ActiveRecord::Query<Role> query(pContext);
  145. const auto result = query
  146. .where("name = ?")
  147. .bind("Developer"s)
  148. .execute();
  149. for (const auto& pRole: result)
  150. {
  151. std::cout << pRole->description() << std::endl;
  152. }
  153. ----
  154. The argument to the `where()` method can be any SQL WHERE clause. Please note
  155. that you must use column names from the actual database tables in the WHERE
  156. clause, not property names.
  157. !Ordering
  158. The results of a Query can be ordered, by calling the `orderBy()` method.
  159. Note that the argument to `orderBy` must be the actual column name in the table,
  160. not the property name of the object. The column name can be followed by
  161. `ASC` or `DESC` to specify the direction.
  162. Poco::ActiveRecord::Query<Role> query(pContext);
  163. const auto result = query
  164. .where("name = ?")
  165. .bind("Developer"s)
  166. .orderBy("name ASC")
  167. .execute();
  168. for (const auto& pRole: result)
  169. {
  170. std::cout << pRole->description() << std::endl;
  171. }
  172. ----
  173. !Paging
  174. The result of a query can be paged, by specifying an offset and a limit.
  175. The offset specifies the index of the first result to be returned, the
  176. limit specifies the maximum number of objects returned.
  177. To retrieve all roles, split up into pages of 10 roles, the following
  178. code could be used:
  179. std::size_t offset = 0;
  180. const std::size_t pageSize = 10;
  181. Poco::ActiveRecord::Query<Role> query(pContext);
  182. bool done = false;
  183. while (!done)
  184. {
  185. const auto result = query
  186. .orderBy("name")
  187. .offset(offset)
  188. .limit(pageSize)
  189. .execute();
  190. offset += result.size();
  191. done = result.empty();
  192. for (const auto& pRole: result)
  193. {
  194. // ...
  195. }
  196. query.reset();
  197. }
  198. ----
  199. In order to re-execute a Query, the `reset()` method must be called first, as is
  200. shown above at the end of the `while` loop.
  201. !Filtering Results
  202. In addition to filtering results with a `WHERE` clause, it's also possible to
  203. filter results with a lambda expression. While `WHERE` is evaluated in the
  204. database engine, and therefore much more efficient, the `filter()` method
  205. allows some additional flexibility.
  206. Poco::ActiveRecord::Query<Role> query(pContext);
  207. query.filter(
  208. [](const Role& role)
  209. {
  210. return role.name() == "Senior Developer";
  211. }
  212. );
  213. const auto result = query.execute();
  214. ----
  215. The lambda expression is passed a const reference to an ActiveRecord object and
  216. must return a `bool`. If `true` is returned, the object is included in the result,
  217. otherwise not.
  218. !Relations
  219. Relations (defined in the XML file as properties with a `references` attribute)
  220. can be accessed via two kinds accessor methods. The first accepts an
  221. ActiveObject::Ptr as parameter or returns it, the second kind takes a key as
  222. parameter or returns it. Accessors that take a key/ID value instead of an
  223. ActiveRecord have their method name suffixed with `ID`.
  224. In the following sample, the `role` property is set with the key value, whereas the
  225. `manager` property is set via the ActiveRecord object.
  226. Employee::Ptr pManager = new Employee;
  227. pManager->name("Bill Lumbergh").ssn("23452343").roleID(3);
  228. pManager->create(pContext);
  229. Employee::Ptr pEmployee = new Employee;
  230. pEmployee->name("Michael Bolton").ssn("123987123").roleID(2).manager(pManager);
  231. pEmployee->create(pContext);
  232. ----
  233. !Auto-Increment Keys and Auto-Generated UUIDs on Insert
  234. ActiveRecord supports auto-incrementing keys when inserting an ActiveRecord. T
  235. o enable this feature, the `autoIncrementID` attribute in the `class` element needs
  236. to be set to `true`.
  237. When inserting such an ActiveRecord object, after executing the `INSERT` statement, the
  238. actual value of the key will be obtained from the database. This is currently
  239. implemented for SQLite, MySQL/MariaDB and PostgreSQL, using appropriate database-specific
  240. mechanisms.
  241. When inserting an ActiveRecord with an all-null UUID, a random UUID will be generated
  242. before executing the `INSERT` statement.
  243. !Keyless Active Records
  244. It is possible to define classes without an ID or primary key property. For these objects,
  245. no `find()` method will be generated, and updating these objects is also not possible
  246. (`update()` will throw a Poco::NotImplementedException).
  247. Keyless ActiveRecord objects can be retrieved by executing a Poco::ActiveRecord::Query.
  248. !!!Compiler XML Reference
  249. !!Supported Data Types
  250. The following data types can be specified for properties in the `type` attribute
  251. and are mapped to the indicated C++ types.
  252. Type in XML C++ Type
  253. ----------------------------
  254. bool bool
  255. char char
  256. int8 Poco::Int8
  257. uint8 Poco::UInt8
  258. int16 Poco::Int16
  259. uint16 Poco::UInt16
  260. int32 Poco::Int32
  261. uint32 Poco::UInt32
  262. int64 Poco::Int64
  263. uint64 Poco::UInt64
  264. float float
  265. double double
  266. dateTime Poco::DateTime
  267. timestamp Poco::Timestamp
  268. time Poco::Data::Time
  269. date Poco::Data::Date
  270. uuid Poco::UUID
  271. string std::string
  272. ----
  273. Note: When creating the underlying database schema, it's the developer's responsibility
  274. to use a database-specific column type compatible with the data type specified in the XML.
  275. !!Elements and Attributes
  276. !The project Element
  277. The `project` element must be the root element in the XML file.
  278. The `project` element accepts the following attributes:
  279. - `namespace`: Specifies the C++ namespace for the generated classes. A multi-level
  280. namespace can be specified, e.g. "MyProject::Data".
  281. - `convertCamelCase`: If set to `true`, property and class names specified in
  282. camel case (e.g., `firstName`) will be converted to snake case (`first_name`) to
  283. identify the respective column or table. Defaults to `false`.
  284. !The class Element
  285. The `class` element must be inside of a `project` element and accepts the following attributes:
  286. - `name`: Specifies the name of the class. Must be a valid C++ class name. Required.
  287. - `table`: Specifies the name of the related database table. If not specified, the
  288. table name will be derived from the class name (see the `convertCamelCase` attribute
  289. in the `project` element).
  290. - `key`: Specifies the name of the primary key column. If not specified, defaults
  291. to `id`.
  292. - `autoIncrementID`: If set to `true`, the primary key is considered to be
  293. auto-incremented. A new ActiveObject is inserted with a NULL primary key, which
  294. causes the database to assign a new key value. The actual key value is then
  295. obtained from the database after executing the `INSERT` statement.
  296. !The property Element
  297. The `property` element must be inside of a `class` element and accepts the following attributes:
  298. - `name`: Specifies the name of the variable, which is also used for the getter and setter
  299. methods. Must be a valid C++ variable or method name. Required.
  300. - `column`: Specifies the name of the related database column. If not specified, the
  301. column name will be derived from the property name (see the `convertCamelCase` attribute
  302. in the `project` element).
  303. - `type`: Specifies the data type of the property. See <*Supported Data Types*> for
  304. a list of supported values. Required.
  305. - `references`: Specifies the name of the target class for a relation. Must be the name
  306. of another class defined in the same XML document.
  307. - `cardinality`: Specifies the cardinality of the relation. The following values can be
  308. specified: `?` means zero or one, `1` means exactly one (default). Additionally, `*` means zero
  309. or more and `+` means one or more, but no accessor is currently generated for the latter
  310. two cardinalities.
  311. - `nullable`: If set to `true`, marks the property or column as nullable. In this case,
  312. the accessor methods will accept or return a Poco::Nullable<> value.