Restructured

This commit is contained in:
thosakwe 2017-07-10 17:49:00 -04:00
parent 2d6ec2ed17
commit 721e465f32
50 changed files with 1004 additions and 484 deletions

1
.idea/.name Normal file
View file

@ -0,0 +1 @@
orm

451
.idea/dbnavigator.xml Normal file
View file

@ -0,0 +1,451 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="true" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DataExportManager">
<export-instructions>
<create-header value="true" />
<quote-values-containing-separator value="true" />
<quote-all-values value="false" />
<value-separator value="" />
<file-name value="" />
<file-location value="" />
<scope value="GLOBAL" />
<destination value="FILE" />
<format value="EXCEL" />
<charset value="windows-1252" />
</export-instructions>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.EditorStateManager">
<last-used-providers />
</component>
<component name="DBNavigator.Project.MethodExecutionManager">
<method-browser />
<execution-history>
<group-entries value="true" />
<execution-inputs />
</execution-history>
<argument-values-cache />
</component>
<component name="DBNavigator.Project.ObjectDependencyManager">
<last-used-dependency-type value="INCOMING" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
<recently-used-interfaces />
</component>
<component name="DBNavigator.Project.Settings">
<connections />
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="NESTED TABLE" enabled="false" />
<object-type name="COLUMN" enabled="false" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE ATTRIBUTE" enabled="false" />
<object-type name="ARGUMENT" enabled="false" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="true" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<tracking-columns>
<columnNames value="" />
<visible value="true" />
<editable value="false" />
</tracking-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-list-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-list-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="CSS" enabled="true" />
<content-type name="SQL" enabled="true" />
<content-type name="PL/SQL" enabled="true" />
<content-type name="JavaScript" enabled="true" />
<content-type name="JSP" enabled="true" />
<content-type name="JSPx" enabled="true" />
<content-type name="ASP" enabled="true" />
<content-type name="YAML" enabled="true" />
<content-type name="Bash" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
<debugger>
<debugger-type value="JDBC" />
<use-generic-runners value="true" />
</debugger>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="SYSTEM_DEFAULT" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
<component name="DBNavigator.Project.StatementExecutionManager">
<execution-variables />
</component>
</project>

View file

@ -1,24 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/test/models/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/tool/packages" />
</content>
<content url="file://$MODULE_DIR$/../serialize">
<excludeFolder url="file://$MODULE_DIR$/../serialize/.pub" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/build" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/test/models/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/../serialize/tool/packages" />
<content url="file://$MODULE_DIR$/angel_orm" />
<content url="file://$MODULE_DIR$/angel_orm_generator">
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/.pub" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/build" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/packages" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/test/models/packages" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/angel_orm_generator/tool/packages" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View file

@ -1,7 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="postgres::build.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/tool/build.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<option name="filePath" value="$PROJECT_DIR$/angel_orm_generator/tool/build.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$/angel_orm_generator" />
<method />
</configuration>
</component>

View file

@ -1 +1,7 @@
language: dart
script: bash tool/.travis.sh
before_script:
- psql -c 'create database angel_orm_test;' -U postgres
- psql -c "CREATE USER angel_orm WITH PASSWORD 'angel_orm';" -U postgres
services:
- postgresql

View file

@ -2,15 +2,31 @@
[![Pub](https://img.shields.io/pub/v/angel_orm.svg)](https://pub.dartlang.org/packages/angel_orm)
[![build status](https://travis-ci.org/angel-dart/orm.svg)](https://travis-ci.org/angel-dart/orm)
**This project is currently in the early stages, and may change at any given
time without warning.**
Source-generated PostgreSQL ORM for use with the
[Angel framework](https://angel-dart.github.io).
Now you can combine the power and flexibility of Angel with a strongly-typed ORM.
Currently supported:
* PostgreSQL
* [Usage](#usage)
* [Model Definitions](#models)
* [MVC Example](#example)
* [Relationships](#relations)
# Usage
You'll need these dependencies in your `pubspec.yaml`:
```yaml
dependencies:
angel_orm: ^1.0.0-alpha
dev_dependencies:
angel_orm_generator: ^1.0.0-alpha
build_runner: ^0.3.0
```
`package:angel_orm_generator` exports two classes that you can include
in a `package:build` flow:
* `PostgreORMGenerator` - Fueled by `package:source_gen`; include this within a `GeneratorBuilder`.
* `SQLMigrationGenerator` - This is its own `Builder`; it generates a SQL schema, as well as a SQL script to drop a generated table.
You should pass an `InputSet` containing your project's models.
# Models
Your model, courtesy of `package:angel_serialize`:
@ -38,6 +54,13 @@ Models can use the `@Alias()` annotation; `package:angel_orm` obeys it.
After building, you'll have access to a `Query` class with strongly-typed methods that
allow to run asynchronous queries without a headache.
**IMPORTANT:** The ORM *assumes* that you are using `package:angel_serialize`, and will only generate code
designed for such a workflow. Save yourself a headache and build models with `angel_serialize`:
https://github.com/angel-dart/serialize
# Example
MVC just got a whole lot easier:
```dart
@ -91,7 +114,7 @@ class CarService extends Controller {
```
# Relations
**NOTE**: This is not yet implemented.
**NOTE**: This is not yet implemented. Expect to see more documentation about this soon.
* `@HasOne()`
* `@HasMany()`

56
angel_orm/.gitignore vendored Normal file
View file

@ -0,0 +1,56 @@
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

7
angel_orm/README.md Normal file
View file

@ -0,0 +1,7 @@
# angel_orm
Runtime support for Angel's ORM. Includes SQL expression generators, as well
as a friendly `PostgreSQLConnectionPool` class that you can use to pool connections
to a PostgreSQL database.
For documentation about the ORM, head to the main project repo:
https://github.com/angel-dart/orm

11
angel_orm/pubspec.yaml Normal file
View file

@ -0,0 +1,11 @@
name: angel_orm
version: 1.0.0-alpha
description: Runtime support for Angel's ORM.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/orm
environment:
sdk: ">=1.19.0"
dependencies:
intl: ^0.15.1
pool: ^1.0.0
postgres: ^0.9.5

View file

@ -0,0 +1,2 @@
analyzer:
strong-mode: true

56
angel_orm_generator/.gitignore vendored Normal file
View file

@ -0,0 +1,56 @@
# See https://www.dartlang.org/tools/private-files.html
# Files and directories created by pub
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 The Angel Framework
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,8 @@
# angel_orm_generator
Source code generators for Angel's ORM.
This package can generate:
* A strongly-typed ORM
* SQL migration scripts
For documentation about the ORM, head to the main project repo:
https://github.com/angel-dart/orm

View file

@ -1,18 +1,14 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_serialize/build_context.dart' as serialize;
import 'package:angel_serialize/context.dart' as serialize;
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/src/find_annotation.dart';
import 'package:angel_serialize_generator/build_context.dart' as serialize;
import 'package:angel_serialize_generator/context.dart' as serialize;
import 'package:build/build.dart';
import 'package:inflection/inflection.dart';
import 'package:recase/recase.dart';
import '../../annotations.dart';
import '../../migration.dart';
import '../../relations.dart';
import 'package:angel_serialize/src/find_annotation.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/source_gen.dart';
import 'postgres_build_context.dart';
// TODO: Should add id, createdAt, updatedAt...
PostgresBuildContext buildContext(
ClassElement clazz,
ORM annotation,

View file

@ -1,19 +1,9 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
import 'package:code_builder/dart/async.dart';
import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart';
import 'package:inflection/inflection.dart';
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/src/utils.dart';
import 'package:source_gen/source_gen.dart';
import '../../annotations.dart';
import '../../migration.dart';
import 'package:angel_serialize/src/find_annotation.dart';
import 'build_context.dart';
import 'postgres_build_context.dart';

View file

@ -1,19 +1,14 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:build/build.dart';
import 'package:code_builder/dart/async.dart';
import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart';
import 'package:inflection/inflection.dart';
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/src/utils.dart';
import 'package:source_gen/source_gen.dart';
import '../../annotations.dart';
import '../../migration.dart';
import 'package:angel_serialize/src/find_annotation.dart';
import 'build_context.dart';
import 'postgres_build_context.dart';
@ -154,6 +149,12 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
// Add insert()...
clazz.addMethod(buildInsertMethod(ctx), asStatic: true);
// Add insertX()
clazz.addMethod(buildInsertModelMethod(ctx), asStatic: true);
// Add updateX()
clazz.addMethod(buildUpdateModelMethod(ctx), asStatic: true);
// Add getAll() => new TodoQuery().get();
clazz.addMethod(
new MethodBuilder('getAll',
@ -315,7 +316,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
}
void _addReturning(StringBuffer buf, PostgresBuildContext ctx) {
buf.write(' RETURNING (');
buf.write(' RETURNING ');
int i = 0;
ctx.fields.forEach((field) {
if (i++ > 0) buf.write(', ');
@ -323,7 +324,7 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
buf.write('"$name"');
});
buf.write(');');
buf.write(';');
}
void _ensureDates(MethodBuilder meth, PostgresBuildContext ctx) {
@ -424,6 +425,44 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
var buf = reference('buf'), whereClause = reference('whereClause');
meth.addStatement(varField('buf',
value: lib$core.StringBuffer
.newInstance([literal('DELETE FROM "${ctx.tableName}"')])));
meth.addStatement(varField('whereClause',
value: reference('where').invoke('toWhereClause', [])));
var ifStmt = ifThen(whereClause.notEquals(literal(null)), [
buf.invoke('write', [literal(' ') + whereClause])
]);
meth.addStatement(ifStmt);
for (var relation in RELATIONS) {
var ref = reference('_$relation');
var upper = relation.toUpperCase();
ifStmt.addStatement(ifThen(ref.property('isNotEmpty'), [
buf.invoke('write', [
literal(' $upper (') +
ref.invoke('join', [literal(', ')]) +
literal(')')
])
]));
}
var litBuf = new StringBuffer();
_addReturning(litBuf, ctx);
meth.addStatement(buf.invoke('write', [literal(litBuf.toString())]));
var streamController = new TypeBuilder('StreamController',
genericTypes: [new TypeBuilder(ctx.modelClassName)]);
meth.addStatement(varField('ctrl',
type: streamController, value: streamController.newInstance([])));
var future =
reference('connection').invoke('query', [buf.invoke('toString', [])]);
_invokeStreamClosure(future, meth);
return meth;
}
@ -438,32 +477,21 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
var id = reference('id');
var connection = reference('connection');
var beforeDelete = reference('__ormBeforeDelete__');
var result = reference('result');
// var __ormBeforeDelete__ = await XQuery.getOne(id, connection);
meth.addStatement(varField('__ormBeforeDelete__',
value: new TypeBuilder(ctx.queryClassName)
.invoke('getOne', [id, connection]).asAwait()));
var buf = new StringBuffer('DELETE FROM "${ctx.tableName}" WHERE id = @id');
_addReturning(buf, ctx);
// await connection.execute('...');
meth.addStatement(varField('result',
value: connection.invoke('execute', [
literal('DELETE FROM "${ctx.tableName}" WHERE id = @id;')
value: connection.invoke('query', [
literal(buf.toString())
], namedArguments: {
'substitutionValues': map({'id': id})
}).asAwait()));
meth.addStatement(ifThen(result.notEquals(literal(1)), [
lib$core.StateError.newInstance([
literal('DELETE query deleted ') +
result +
literal(' row(s), instead of exactly 1 row.')
])
]));
// return __ormBeforeDelete__
meth.addStatement(beforeDelete.asReturn());
meth.addStatement(
reference('parseRow').call([result[literal(0)]]).asReturn());
return meth;
}
@ -501,43 +529,85 @@ class PostgresORMGenerator extends GeneratorForAnnotation<ORM> {
}
});
buf.write(');');
buf.write(')');
// meth.addStatement(lib$core.print.call([literal(buf.toString())]));
_addReturning(buf, ctx);
_ensureDates(meth, ctx);
var substitutionValues = _buildSubstitutionValues(ctx);
// connection.execute...
var connection = reference('connection'), nRows = reference('nRows');
meth.addStatement(varField('nRows',
value: connection.invoke('execute', [
literal(buf.toString())
var connection = reference('connection');
var query = literal(buf.toString());
var result = reference('result');
meth.addStatement(varField('result',
value: connection.invoke('query', [
query
], namedArguments: {
'substitutionValues': map(substitutionValues)
}).asAwait()));
meth.addStatement(
reference('parseRow').call([result[literal(0)]]).asReturn());
return meth;
}
meth.addStatement(ifThen(nRows < literal(1), [
lib$core.StateError.newInstance([
literal('Insertion into "${ctx.tableName}" table failed.')
]).asThrow()
MethodBuilder buildInsertModelMethod(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var meth = new MethodBuilder('insert${rc.pascalCase}',
returnType: new TypeBuilder('Future',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
meth.addPositional(
parameter(rc.snakeCase, [new TypeBuilder(ctx.modelClassName)]));
Map<String, ExpressionBuilder> args = {};
var ref = reference(rc.snakeCase);
ctx.fields.forEach((f) {
if (f.name != 'id') args[f.name] = ref.property(f.name);
});
meth.addStatement(new TypeBuilder(ctx.queryClassName)
.invoke('insert', [reference('connection')], namedArguments: args)
.asReturn());
return meth;
}
MethodBuilder buildUpdateModelMethod(PostgresBuildContext ctx) {
var rc = new ReCase(ctx.modelClassName);
var meth = new MethodBuilder('update${rc.pascalCase}',
returnType: new TypeBuilder('Future',
genericTypes: [new TypeBuilder(ctx.modelClassName)]));
meth.addPositional(
parameter('connection', [new TypeBuilder('PostgreSQLConnection')]));
meth.addPositional(
parameter(rc.snakeCase, [new TypeBuilder(ctx.modelClassName)]));
// var query = new XQuery();
var ref = reference(rc.snakeCase);
var query = reference('query');
meth.addStatement(varField('query',
value: new TypeBuilder(ctx.queryClassName).newInstance([])));
// query.where.id.equals(x.id);
meth.addStatement(query.property('where').property('id').invoke('equals', [
lib$core.int.invoke('parse', [ref.property('id')])
]));
// Query the last value...
/*
var currval = await connection.query("SELECT * FROM cars WHERE id = currval(pg_get_serial_sequence('cars', 'id'));");
print(currval);
return parseRow(currval[0]);
*/
// return query.update(connection, ...).first;
Map<String, ExpressionBuilder> args = {};
ctx.fields.forEach((f) {
if (f.name != 'id') args[f.name] = ref.property(f.name);
});
var update =
query.invoke('update', [reference('connection')], namedArguments: args);
meth.addStatement(update.property('first').asReturn());
var currVal = reference('currVal');
meth.addStatement(varField('currVal',
value: connection.invoke('query', [
literal(
'SELECT * FROM "${ctx.tableName}" WHERE id = currval(pg_get_serial_sequence(\'${ctx.tableName}\', \'id\'));')
]).asAwait()));
meth.addStatement(
reference('parseRow').call([currVal[literal(0)]]).asReturn());
return meth;
}

View file

@ -1,11 +1,9 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:angel_orm/angel_orm.dart';
import 'package:angel_serialize_generator/context.dart';
import 'package:build/build.dart';
import 'package:angel_serialize/context.dart';
import '../../annotations.dart';
import '../../migration.dart';
import '../../relations.dart';
class PostgresBuildContext extends BuildContext {
DartType _dateTimeTypeCache;

View file

@ -0,0 +1,20 @@
name: angel_orm_generator
version: 1.0.0-alpha
description: Code generators for Angel's ORM.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/orm
environment:
sdk: ">=1.19.0"
dependencies:
angel_orm: ^1.0.0-alpha
angel_serialize_generator: ^1.0.0-alpha
code_builder: ^1.0.0
inflection: ^0.4.1
recase: ^1.0.0
source_gen: ^0.6.0
dev_dependencies:
angel_diagnostics: ^1.0.0
angel_framework: ^1.0.0
angel_test: ^1.0.0
build_runner: ^0.3.0
test: ^0.12.0

View file

@ -106,12 +106,26 @@ main() {
expect(cars, isEmpty);
});
test('delete', () async {
var query = new CarQuery();
test('delete stream', () async {
var query = new CarQuery()..where.make.equals('Ferrari');
var cars = await query.delete(connection).toList();
expect(cars, hasLength(1));
expect(cars.first.toJson(), ferrari.toJson());
});
test('update', () async {
var query = new CarQuery()..where.id.equals(int.parse(ferrari.id));
var cars = await query.update(connection, make: 'Hyundai').toList();
expect(cars, hasLength(1));
expect(cars.first.make, 'Hyundai');
});
test('update car', () async {
var cloned = ferrari.clone()..make = 'Angel';
var car = await CarQuery.updateCar(connection, cloned);
print(car.toJson());
expect(car.toJson(), cloned.toJson());
});
});
});
@ -126,8 +140,25 @@ main() {
expect(car.make, 'Honda');
expect(car.description, 'Hello');
expect(car.familyFriendly, isTrue);
expect(DATE_YMD_HMS.format(car.recalledAt), DATE_YMD_HMS.format(recalledAt));
expect(
DATE_YMD_HMS.format(car.recalledAt), DATE_YMD_HMS.format(recalledAt));
expect(car.createdAt, allOf(isNotNull, equals(car.updatedAt)));
});
test('insert car', () async {
var recalledAt = new DateTime.now();
var beetle = new Car(
make: 'Beetle',
description: 'Herbie',
familyFriendly: true,
recalledAt: recalledAt);
var car = await CarQuery.insertCar(connection, beetle);
print(car.toJson());
expect(car.make, beetle.make);
expect(car.description, beetle.description);
expect(car.familyFriendly, beetle.familyFriendly);
expect(DATE_YMD_HMS.format(car.recalledAt),
DATE_YMD_HMS.format(beetle.recalledAt));
});
});
}

View file

@ -46,4 +46,8 @@ class Author extends _Author {
};
static Author parse(Map map) => new Author.fromJson(map);
Author clone() {
return new Author.fromJson(toJson());
}
}

View file

@ -95,8 +95,7 @@ class AuthorQuery {
var __ormNow__ = new DateTime.now();
var ctrl = new StreamController<Author>();
connection.query(
buf.toString() +
' RETURNING ("id", "name", "created_at", "updated_at");',
buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
@ -108,38 +107,69 @@ class AuthorQuery {
return ctrl.stream;
}
Stream<Author> delete(PostgreSQLConnection connection) async {}
Stream<Author> delete(PostgreSQLConnection connection) {
var buf = new StringBuffer('DELETE FROM "authors"');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
if (_and.isNotEmpty) {
buf.write(' AND (' + _and.join(', ') + ')');
}
if (_or.isNotEmpty) {
buf.write(' OR (' + _or.join(', ') + ')');
}
if (_not.isNotEmpty) {
buf.write(' NOT (' + _not.join(', ') + ')');
}
}
buf.write(' RETURNING "id", "name", "created_at", "updated_at";');
StreamController<Author> ctrl = new StreamController<Author>();
connection.query(buf.toString()).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Author> deleteOne(
int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await AuthorQuery.getOne(id, connection);
var result = await connection.execute(
'DELETE FROM "authors" WHERE id = @id;',
var result = await connection.query(
'DELETE FROM "authors" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
return parseRow(result[0]);
}
static Future<Author> insert(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var nRows = await connection.execute(
'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);',
var result = await connection.query(
'INSERT INTO "authors" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
if (nRows < 1) {
throw new StateError('Insertion into "authors" table failed.');
return parseRow(result[0]);
}
var currVal = await connection.query(
'SELECT * FROM "authors" WHERE id = currval(pg_get_serial_sequence(\'authors\', \'id\'));');
return parseRow(currVal[0]);
static Future<Author> insertAuthor(
PostgreSQLConnection connection, Author author) {
return AuthorQuery.insert(connection,
name: author.name,
createdAt: author.createdAt,
updatedAt: author.updatedAt);
}
static Future<Author> updateAuthor(
PostgreSQLConnection connection, Author author) {
var query = new AuthorQuery();
query.where.id.equals(int.parse(author.id));
return query
.update(connection,
name: author.name,
createdAt: author.createdAt,
updatedAt: author.updatedAt)
.first;
}
static Stream<Author> getAll(PostgreSQLConnection connection) =>

View file

@ -51,4 +51,8 @@ class Book extends _Book {
};
static Book parse(Map map) => new Book.fromJson(map);
Book clone() {
return new Book.fromJson(toJson());
}
}

View file

@ -97,8 +97,7 @@ class BookQuery {
var __ormNow__ = new DateTime.now();
var ctrl = new StreamController<Book>();
connection.query(
buf.toString() +
' RETURNING ("id", "name", "created_at", "updated_at");',
buf.toString() + ' RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
@ -110,36 +109,64 @@ class BookQuery {
return ctrl.stream;
}
Stream<Book> delete(PostgreSQLConnection connection) async {}
Stream<Book> delete(PostgreSQLConnection connection) {
var buf = new StringBuffer('DELETE FROM "books"');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
if (_and.isNotEmpty) {
buf.write(' AND (' + _and.join(', ') + ')');
}
if (_or.isNotEmpty) {
buf.write(' OR (' + _or.join(', ') + ')');
}
if (_not.isNotEmpty) {
buf.write(' NOT (' + _not.join(', ') + ')');
}
}
buf.write(' RETURNING "id", "name", "created_at", "updated_at";');
StreamController<Book> ctrl = new StreamController<Book>();
connection.query(buf.toString()).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
return ctrl.stream;
}
static Future<Book> deleteOne(int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await BookQuery.getOne(id, connection);
var result = await connection.execute('DELETE FROM "books" WHERE id = @id;',
var result = await connection.query(
'DELETE FROM "books" WHERE id = @id RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
return parseRow(result[0]);
}
static Future<Book> insert(PostgreSQLConnection connection,
{String name, DateTime createdAt, DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var nRows = await connection.execute(
'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt);',
var result = await connection.query(
'INSERT INTO "books" ("name", "created_at", "updated_at") VALUES (@name, @createdAt, @updatedAt) RETURNING "id", "name", "created_at", "updated_at";',
substitutionValues: {
'name': name,
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
if (nRows < 1) {
throw new StateError('Insertion into "books" table failed.');
return parseRow(result[0]);
}
var currVal = await connection.query(
'SELECT * FROM "books" WHERE id = currval(pg_get_serial_sequence(\'books\', \'id\'));');
return parseRow(currVal[0]);
static Future<Book> insertBook(PostgreSQLConnection connection, Book book) {
return BookQuery.insert(connection,
name: book.name, createdAt: book.createdAt, updatedAt: book.updatedAt);
}
static Future<Book> updateBook(PostgreSQLConnection connection, Book book) {
var query = new BookQuery();
query.where.id.equals(int.parse(book.id));
return query
.update(connection,
name: book.name,
createdAt: book.createdAt,
updatedAt: book.updatedAt)
.first;
}
static Stream<Book> getAll(PostgreSQLConnection connection) =>

View file

@ -72,4 +72,8 @@ class Car extends _Car {
};
static Car parse(Map map) => new Car.fromJson(map);
Car clone() {
return new Car.fromJson(toJson());
}
}

View file

@ -104,7 +104,7 @@ class CarQuery {
var ctrl = new StreamController<Car>();
connection.query(
buf.toString() +
' RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");',
' RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";',
substitutionValues: {
'make': make,
'description': description,
@ -120,10 +120,24 @@ class CarQuery {
}
Stream<Car> delete(PostgreSQLConnection connection) {
var query = 'DELETE FROM "cars" RETURNING ("id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at");';
var buf = new StringBuffer('DELETE FROM "cars"');
var whereClause = where.toWhereClause();
if (whereClause != null) {
buf.write(' ' + whereClause);
if (_and.isNotEmpty) {
buf.write(' AND (' + _and.join(', ') + ')');
}
if (_or.isNotEmpty) {
buf.write(' OR (' + _or.join(', ') + ')');
}
if (_not.isNotEmpty) {
buf.write(' NOT (' + _not.join(', ') + ')');
}
}
buf.write(
' RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";');
StreamController<Car> ctrl = new StreamController<Car>();
connection.execute(query).then((rows) {
print('Rows: $rows');
connection.query(buf.toString()).then((rows) {
rows.map(parseRow).forEach(ctrl.add);
ctrl.close();
}).catchError(ctrl.addError);
@ -131,15 +145,10 @@ class CarQuery {
}
static Future<Car> deleteOne(int id, PostgreSQLConnection connection) async {
var __ormBeforeDelete__ = await CarQuery.getOne(id, connection);
var result = await connection.execute('DELETE FROM "cars" WHERE id = @id;',
var result = await connection.query(
'DELETE FROM "cars" WHERE id = @id RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";',
substitutionValues: {'id': id});
if (result != 1) {
new StateError('DELETE query deleted ' +
result +
' row(s), instead of exactly 1 row.');
}
return __ormBeforeDelete__;
return parseRow(result[0]);
}
static Future<Car> insert(PostgreSQLConnection connection,
@ -150,8 +159,8 @@ class CarQuery {
DateTime createdAt,
DateTime updatedAt}) async {
var __ormNow__ = new DateTime.now();
var nRows = await connection.execute(
'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt);',
var result = await connection.query(
'INSERT INTO "cars" ("make", "description", "family_friendly", "recalled_at", "created_at", "updated_at") VALUES (@make, @description, @familyFriendly, @recalledAt, @createdAt, @updatedAt) RETURNING "id", "make", "description", "family_friendly", "recalled_at", "created_at", "updated_at";',
substitutionValues: {
'make': make,
'description': description,
@ -160,12 +169,31 @@ class CarQuery {
'createdAt': createdAt != null ? createdAt : __ormNow__,
'updatedAt': updatedAt != null ? updatedAt : __ormNow__
});
if (nRows < 1) {
throw new StateError('Insertion into "cars" table failed.');
return parseRow(result[0]);
}
var currVal = await connection.query(
'SELECT * FROM "cars" WHERE id = currval(pg_get_serial_sequence(\'cars\', \'id\'));');
return parseRow(currVal[0]);
static Future<Car> insertCar(PostgreSQLConnection connection, Car car) {
return CarQuery.insert(connection,
make: car.make,
description: car.description,
familyFriendly: car.familyFriendly,
recalledAt: car.recalledAt,
createdAt: car.createdAt,
updatedAt: car.updatedAt);
}
static Future<Car> updateCar(PostgreSQLConnection connection, Car car) {
var query = new CarQuery();
query.where.id.equals(int.parse(car.id));
return query
.update(connection,
make: car.make,
description: car.description,
familyFriendly: car.familyFriendly,
recalledAt: car.recalledAt,
createdAt: car.createdAt,
updatedAt: car.updatedAt)
.first;
}
static Stream<Car> getAll(PostgreSQLConnection connection) =>

View file

@ -1,9 +1,10 @@
import 'package:build_runner/build_runner.dart';
import 'package:source_gen/source_gen.dart';
import 'package:angel_orm/builder.dart';
import 'package:angel_serialize/builder.dart';
import 'package:angel_orm_generator/angel_orm_generator.dart';
import 'package:angel_serialize_generator/angel_serialize_generator.dart';
final InputSet MODELS = new InputSet('angel_orm', const ['test/models/*.dart']);
final InputSet MODELS =
new InputSet('angel_orm_generator', const ['test/models/*.dart']);
final PhaseGroup PHASES = new PhaseGroup()
..addPhase(new Phase()

View file

@ -1,296 +0,0 @@
import 'dart:async';
import 'dart:mirrors';
import 'package:analyzer/dart/element/element.dart';
import 'package:angel_serialize/angel_serialize.dart';
import 'package:build/build.dart';
import 'package:code_builder/dart/async.dart';
import 'package:code_builder/dart/core.dart';
import 'package:code_builder/code_builder.dart';
import 'package:inflection/inflection.dart';
import 'package:path/path.dart' as p;
import 'package:recase/recase.dart';
import 'package:source_gen/src/annotation.dart';
import 'package:source_gen/source_gen.dart';
import 'package:query_builder_sql/query_builder_sql.dart';
import '../annotations.dart';
// TODO: whereXLessThan, greaterThan, etc.
final RegExp _leadingDot = new RegExp(r'^\.+');
const List<String> QUERY_DO_NOT_OVERRIDE = const ['when'];
typedef Iterable<ExpressionBuilder> SuperArgumentProvider(
ORM model, ClassElement clazz);
class AngelQueryBuilderGenerator extends GeneratorForAnnotation<ORM> {
ClassMirror _baseRepositoryClassMirror;
final List<String> _imports = [
'dart:async',
'package:query_builder/query_builder.dart'
];
final Map<String, TypeBuilder> _constructorParams = {};
SuperArgumentProvider _superArgProvider;
AngelQueryBuilderGenerator(Type baseRepositoryQueryClass,
{Iterable<String> additonalImports: const [],
Map<String, TypeBuilder> constructorParams: const {},
SuperArgumentProvider superArgProvider}) {
_baseRepositoryClassMirror = reflectClass(baseRepositoryQueryClass);
_imports.addAll(additonalImports ?? []);
_constructorParams.addAll(constructorParams ?? {});
_superArgProvider = superArgProvider ??
(annotation, clazz) => [
literal(annotation.tableName?.isNotEmpty == true
? annotation.tableName
: pluralize(new ReCase(clazz.name.substring(1)).snakeCase))
];
}
factory AngelQueryBuilderGenerator.postgresql() =>
new AngelQueryBuilderGenerator(SqlRepositoryQuery, constructorParams: {
'connection': new TypeBuilder('PostgreSQLConnection')
}, additonalImports: [
'package:postgres/postgres.dart',
'package:query_builder_sql/query_builder_sql.dart'
]);
@override
Future<String> generateForAnnotatedElement(
Element element, ORM annotation, BuildStep buildStep) async {
if (element.kind != ElementKind.CLASS)
throw 'Only classes may be annotated with @model.';
var lib = generatePostgresLibrary(element, annotation, buildStep.inputId);
return prettyToSource(lib.buildAst());
}
LibraryBuilder generatePostgresLibrary(
ClassElement clazz, ORM annotation, AssetId inputId) {
if (!clazz.name.startsWith('_'))
throw 'Classes annotated with @model must have names starting with an underscore.';
var lib = new LibraryBuilder();
lib.addDirectives(_imports.map((p) => new ImportBuilder(p)));
lib.addDirective(new ImportBuilder(p.basename(inputId.path)));
// Find all aliases...
Map<String, String> aliases = {};
clazz.fields.forEach((field) {
var aliasAnnotation = field.metadata
.firstWhere((ann) => matchAnnotation(Alias, ann), orElse: () => null);
if (aliasAnnotation != null) {
var alias = instantiateAnnotation(aliasAnnotation) as Alias;
aliases[field.name] = alias.name;
}
});
lib.addMember(generateRepositoryClass(clazz, aliases));
lib.addMember(generateRepositoryQueryClass(clazz, annotation, aliases));
return lib;
}
ClassBuilder generateRepositoryClass(
ClassElement clazz, Map<String, String> aliases) {
var genClassName = clazz.name.substring(1) + 'Repository';
var genQueryClassName = genClassName + 'Query';
var genClass = new ClassBuilder(genClassName);
var genQueryType = new TypeBuilder(genQueryClassName);
// Add `connection` field + constructor
var genConstructor = new ConstructorBuilder();
_constructorParams.forEach((name, type) {
genClass.addField(varFinal(name, type: type));
genConstructor.addPositional(parameter(name), asField: true);
});
genClass.addConstructor(genConstructor);
// Add an all method
genClass.addMethod(new MethodBuilder('all',
returnType: new TypeBuilder(genQueryClassName),
returns: new TypeBuilder(genQueryClassName)
.newInstance([reference('connection')])));
// For each field, add a whereX() method...
clazz.fields
.map((field) => generateWhereFieldMethod(
field, reference('all').call([]), genQueryType, aliases))
.forEach(genClass.addMethod);
return genClass;
}
ClassBuilder generateRepositoryQueryClass(
ClassElement clazz, ORM annotation, Map<String, String> aliases) {
var modelClassName = clazz.name.substring(1);
var genClassName = clazz.name.substring(1) + 'RepositoryQuery';
var genClass = new ClassBuilder(genClassName,
asExtends: new TypeBuilder(
MirrorSystem.getName(_baseRepositoryClassMirror.simpleName),
genericTypes: [new TypeBuilder(modelClassName)]));
var genQueryType = new TypeBuilder(genClassName);
// Add `connection` field + constructor
var genConstructor = new ConstructorBuilder(
invokeSuper: _superArgProvider(annotation, clazz));
_constructorParams.forEach((name, type) {
genClass.addField(varFinal(name, type: type));
genConstructor.addPositional(parameter(name), asField: true);
});
genClass.addConstructor(genConstructor);
// For each field, add a whereX() method...
clazz.fields
.map((field) => generateWhereFieldMethod(
field, explicitThis, genQueryType, aliases))
.forEach(genClass.addMethod);
// Add orWhereX()
clazz.fields
.map((f) => generateOrWhereFieldMethod(genQueryType, f))
.forEach(genClass.addMethod);
// Override any query methods
_baseRepositoryClassMirror.instanceMembers.forEach((sym, method) {
// Skip setters, etc.
if (!method.isRegularMethod) return;
// Only if return type contains 'RepositoryQuery'
var methodName = MirrorSystem.getName(sym);
if (QUERY_DO_NOT_OVERRIDE.contains(methodName)) return;
var returnTypeName = MirrorSystem.getName(method.returnType.simpleName);
if (returnTypeName.contains('RepositoryQuery')) {
var overriddenMethod =
new MethodBuilder(methodName, returnType: genQueryType);
// Add @override
overriddenMethod.addAnnotation(lib$core.override);
// Find all positional and named args
List<String> args = [];
List<String> named = [];
method.parameters.forEach((param) {
var paramName = MirrorSystem.getName(param.simpleName);
var typeName = MirrorSystem.getName(param.type.simpleName);
var paramType = new TypeBuilder(typeName);
var genParam = parameter(paramName, [paramType]);
if (!param.isNamed) {
args.add(paramName);
overriddenMethod.addPositional(
param.isOptional ? genParam.asOptional() : genParam);
} else {
overriddenMethod.addNamed(genParam);
named.add(paramName);
}
});
// Invoke super
overriddenMethod.addStatement(reference('super')
.invoke(methodName, args.map(reference),
namedArguments: named.fold<Map<String, ExpressionBuilder>>(
{}, (out, k) => out..[k] = reference(k)))
.asReturn());
genClass.addMethod(overriddenMethod);
}
});
// Override toSql to put keys in desired order
// TODO: Override toSql
// Add get()
genClass.addMethod(generateGetMethod(clazz, modelClassName, aliases));
return genClass;
}
MethodBuilder generateGetMethod(
ClassElement clazz, String modelClassName, Map<String, String> aliases) {
var meth = new MethodBuilder('get')..addAnnotation(lib$core.override);
// Map rows to model...
var mapRowsToModel = new MethodBuilder.closure()
..addPositional(parameter('rows'));
// First, figure out which rows we fetched...
//
// var requestedKeys = whereFields.keys.isNotEmpty ? whereFields.keys : [<all fields...>];
//var allModelFields = clazz.fields
// .map((f) => aliases.containsKey(f.name) ? aliases[f.name] : f.name);
//var whereFieldsKeys = reference('whereFields').property('keys');
// return new Stream<>.fromFuture(...)
meth.addStatement(lib$async.Stream.newInstance([
reference('connection')
.invoke('query', [reference('toSql').call([])]).invoke(
'then', [mapRowsToModel])
], constructor: 'fromFuture').asReturn());
return meth;
}
MethodBuilder generateWhereFieldMethod(
FieldElement field,
ExpressionBuilder baseQuery,
TypeBuilder returnType,
Map<String, String> aliases) {
var rc = new ReCase(field.name);
var whereMethod =
new MethodBuilder('where${rc.pascalCase}', returnType: returnType);
var columnName =
aliases.containsKey(field.name) ? aliases[field.name] : field.name;
whereMethod.addPositional(
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
if (field.type.name == 'DateTime') {
// Add named `{time: true}`
whereMethod.addNamed(
parameter('time', [lib$core.bool]).asOptional(literal(true)));
// return all().whereDate('x', x, time: time != false);
// return all().where('x', x);
whereMethod.addStatement(baseQuery.invoke('whereDate', [
literal(columnName),
reference(field.name)
], namedArguments: {
'time': reference('time').notEquals(literal(false))
}).asReturn());
} else {
// return all().where('x', x);
whereMethod.addStatement(baseQuery.invoke(
'where', [literal(columnName), reference(field.name)]).asReturn());
}
return whereMethod;
}
MethodBuilder generateOrWhereFieldMethod(
TypeBuilder genQueryClassType, FieldElement field) {
var rc = new ReCase(field.name);
var orWhereMethod = new MethodBuilder('orWhere' + rc.pascalCase,
returnType: genQueryClassType);
orWhereMethod.addPositional(
parameter(field.name, [new TypeBuilder(field.type.displayName)]));
if (field.type.name == 'DateTime') {
orWhereMethod.addNamed(parameter('time', [lib$core.bool]));
orWhereMethod.addStatement(reference('or').call([
reference('where' + rc.pascalCase).call([
reference(field.name)
], namedArguments: {
'time': reference('time').notEquals(literal(false))
})
]).asReturn());
} else {
orWhereMethod.addStatement(reference('or').call([
reference('where' + rc.pascalCase).call([reference(field.name)])
]).asReturn());
}
return orWhereMethod;
}
}

View file

@ -1,24 +0,0 @@
name: angel_orm
version: 0.0.0
description: Source-generated ORM for use with the Angel framework.
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_mongo
dependencies:
angel_framework: ">=1.0.0-dev < 2.0.0"
code_builder: ^1.0.0
id: ^1.0.0
inflection: ^0.4.1
intl: ^0.15.1
pool: ^1.0.0
postgres: ">=0.9.5 <1.0.0"
query_builder_sql: ^1.0.0-alpha
recase: ^1.0.0
source_gen: ^0.6.0
dev_dependencies:
angel_diagnostics: ">=1.0.0 <2.0.0"
angel_serialize:
path: ../serialize
angel_test: ">=1.0.0 <2.0.0"
build_runner: ^0.3.0
http: ">= 0.11.3 < 0.12.0"
test: ">= 0.12.13 < 0.13.0"

5
tool/.travis.sh Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
cd angel_orm_generator
pub get
dart tool/build.dart
POSTGRES_USERNAME="angel_orm" POSTGRES_PASSWORD="angel_orm" pub run test