Skip to content

Added initial support for Composite Foreign Keys and Indexes #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ preprocessor/target
common/target
build
*.keystore
*.~*
projectBackupFiles
129 changes: 120 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ annotations and CRUD classes expose an expressive api for executing SQLite queri
preprocessors on Android.*
```groovy
dependencies {
apt 'com.memtrip.sqlking:preprocessor:1.1.5'
compile 'com.memtrip.sqlking:client:1.1.5'
apt 'com.memtrip.sqlking:preprocessor:1.1.6'
compile 'com.memtrip.sqlking:client:1.1.6'
}
```

Expand Down Expand Up @@ -230,12 +230,27 @@ User[] users = Select.getBuilder()
####Joins####
Joins can be performed using the `InnerJoin`, `LeftOutJoin`, `CrossInnerJoin`, `NaturalInnerJoin`, `NaturalLeftOuterJoin` classes.
The target table for the join must be defined as an @Column, the object will be populated with any join results.
Single Column Constraints and Foreignk Keys can also be defined.

```java
@Table
public class Comment {
@Column(index = true) int id;
@Column int userId;
@Column([@ForeignKey // Single column ForeignKey (not required for joins, but will be enforced by SQLite DBMS)
(
foreignTablename="User",
localColumnNames ={"user_id"},
foreignColumnNames = {"id"},
[update/deleteRule=RIRule.Cascade|...][,...]
)
int userId;]
@Constraints = {@Constraint // Single column Constraints, see SQLite documentation fot `table_constraint`
(
constraintName = "constraintName", // will be created as "table_name_constraint_name_constraint". the constraint name must be unique within the constraints for the table
expression = "some expression eg, PRIMARY|FOREIGN KEY, UNIQUE, CHECK"
[,@onConflict (ConflictAction.ROLLBACK|ABORT|FAIL|etc)] // optional ON CONFLICT clause, if required and appropriate
)[, ...] // optional additional @Constraint statements

@Column User user; // The target table for a potential join

public int getId() {
Expand Down Expand Up @@ -265,7 +280,7 @@ public class Comment {

@Table
public class User {
@Column(index = true) int id;
@Column(index = true) int id; // Single column index (short form syntax)

public int getId() {
return id;
Expand All @@ -282,13 +297,18 @@ Comment[] comments = Select.getBuilder()

User user = comments[0].getUser(); // The nested User object is populated by the join
```
####Primary Key####
An auto incrementing primary key can be defined using:

####Primary Keys####
Primary Keys can be defined at Table-level or Column-level, but not both, and only one Primary can be defined per table;
a Primary Key at Table-level can contain multiple columns in the key, but Column-level Primary Keys are only defined for the single column on which it is annotated.

An auto incrementing primary key can be defined on an `int` or `long` column using:

```java
@Table
public class Data {
@Column(primary_key = true, auto_increment = true) int id;
@Column(primary_key = @PrimaryKey(active = true, true, auto_increment = true)
int id;

public int getId() {
return id;
Expand All @@ -300,13 +320,104 @@ public class Data {
}
```

Table level Primary keys are annotated with the following syntax:

```java
@Table(
primaryKey = @PrimaryKey(
active = true,
columns = {"id"}, // Note, multiple columns can be defined, but in this case, no auto-increment is allowed
auto_increment = true
)
)
public class Post {
@Column int id;
@Column String title;
@Column byte[] blob;
@Column long timestamp;
@Column User user;
@Column Data data;

```

####Table Constraints, Composite Indexes and Foreign Keys####
Multiple table Constraints, Composite Indexes and Composite Foreign Keys can be defined for the table with the

```java
@Table (
foreignkeys = {@ForeignKey ( // not required for joins (yet), but will be enforced by SQLite RDDBMS)
foreignTableName = "xxxx", // will be created as "fk_localTableName_foreignTableName_n", _n increments for multiple links to the same foreign table
localColumnNames = {"localcolumn"[, ...]}, // Multiple columns possible
},
foreignColumnNames = {"foreignColumn"[, ...]// Multiple columns possible
[,updateRule = <RIRule.SetNull
|RIRule.SetDefault
|RIRule.Cascade // The default
|RIRule.Restrict
|RIRule.NotNull
|RIRule.NoAction>]
[,deleteRule = <RIRule.SetNull
|RIRule.SetDefault
|RIRule.Cascade
|RIRule.Restrict // The default
|RIRule.NotNull
|RIRule.NoAction>]

}
)[, ...] // optional additional @ForeignKey statements
},
indexes = {@Index (
indexName = "index_name", // will be created as "table_name_index_name_index". index_name must be unique within the indexes on the parent table
columns = {@IndexColumn (
column = "column1"
[,sortOrder = SortOrder.ASC|SortOrder.DESC] // optional column sort order (default is SortOrder.ASC)
)[, ...] // optional additional @IndexColumn statements
}
)[, ...] // optional additional @Index statements
},
constraints = {@Constraint (
constraintName = "constraintName", // will be created as "table_name_constraint_name_constraint". the constraint name must be unique within the constraints for the table
expression = "some expression eg, PRIMARY|FOREIGN KEY, UNIQUE, CHECK" // see SQLite documentation fot `table_constraint` clause
[,@onConflict (ConflictAction.ROLLBACK|ABORT|FAIL|IGNORE|REPLACE)] // as appropriate, if required
)[, ...] // optional additional @Constraint statements
}
)
```

####Triggers####
Triggers can be defined at the Table Level with the following syntax:

```java
@Table (
triggers = {@Trigger(triggerName = "my_trigger_name",
[,triggerTime = <TriggerTime.BEFORE // optional; only one trigger time is allowed, but it is not required (default is TriggerTime.NONE)
| TriggerTime.AFTER
| TriggerTime.INSTEAD_OF
| TriggerTime.NONE>]
[,triggerType = <TriggerType.INSERT // optional; only one trigger type is allowed, but is not required (default is triggerType.NONE)
| TriggerType.UPDATE
| TriggerType.DELETE
| TriggerType.NONE>]
[,updateOfColumns = { <column>[,...] }] // one or more columns
[,forEachRow = <true|false>] // optional; defaults to false. SQLite does not currently support FOR_EACH_STATEMENT
[,whenExpression = <"my_when_expression">] // optional; see SQLite document for the 'my_when_expression' syntax
<,statement = "my_trigger_statement"> // see SQLite document for the 'my_trigger_expression' syntax
)[,...] // optional addition @Trigger statements
}
)

```


####Tests####
The `tests/java/com/memtrip/sqlking` package contains a full set of unit and integration tests. The
tests can be used as a good reference on how to structure queries.

####TODO####
- Validate that object relationships defined by @Column are annotated with @Table
- Validate that auto_increment columns must be int or long
- @Table annotation should support foreign_key functionality
- @NotNull annotation and handle this validation in the software layer
- Composite Foreign Key Constraints
- Add table Alias support for Queries, Foreign keys and Joins
- Allow for join creation based on Foreign key Annotations (including aliases)
- Add support for database version upgrades scripts, so that exisiting data is retained during an upgrade
- Add validation and tests for triggers and constraints
6 changes: 6 additions & 0 deletions build-allw.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cd common
cmd /C mvn clean install
cd ..\preprocessor
cmd /C mvn clean install
cd ..
cmd /C gradlew build install
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ buildscript {
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
}
}
8 changes: 4 additions & 4 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ext {
siteUrl = 'https://github.com/memtrip/SQLKing'
gitUrl = 'https://github.com/memtrip/SQLKing.git'

libraryVersion = '1.1.5'
libraryVersion = '1.1.8'

developerId = 'samkirton'
developerName = 'Samuel Kirton'
Expand All @@ -44,7 +44,7 @@ android {
minSdkVersion 11
targetSdkVersion 24
versionCode 7
versionName "1.1.1"
versionName "1.1.8"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -137,11 +137,11 @@ install {
}

dependencies {
compile 'com.memtrip.sqlking:common:1.1.5'
compile 'com.memtrip.sqlking:common:1.1.8'
compile 'io.reactivex:rxjava:1.1.1'
compile 'io.reactivex:rxandroid:1.1.0'

androidTestApt 'com.memtrip.sqlking:preprocessor:1.1.5'
androidTestApt 'com.memtrip.sqlking:preprocessor:1.1.8'

androidTestCompile(
'com.android.support.test:runner:0.3',
Expand Down
66 changes: 41 additions & 25 deletions client/src/main/java/com/memtrip/sqlking/database/ClauseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,18 @@ public class ClauseHelper {
private static final String AND = "AND";
private static final String OR = "OR";

protected ClauseHelper() { }
protected ClauseHelper() {}

public String getClause(Clause[] clause) {
StringBuilder clauseBuilder = new StringBuilder();

if (clause != null) {
for (Clause item : clause) {
clauseBuilder.append(getClause(item));
String strClause = getClause(item);
// clauseBuilder.append(getClause(item));
clauseBuilder.append(strClause);
}
}

return clauseBuilder.toString();
}

Expand All @@ -74,7 +75,9 @@ private String getClause(Clause clause) {
clauseBuilder.append(buildOnCondition((On) clause));
} else if (clause instanceof And) {
clauseBuilder.append(BRACKET_START);

And and = (And)clause;

for (Clause item : and.getClause()) {
clauseBuilder.append(getClause(item));
clauseBuilder.append(SPACE);
Expand All @@ -85,6 +88,7 @@ private String getClause(Clause clause) {
// remove the excess AND with its 2 spaces
clauseBuilder.delete(clauseBuilder.length() - 5, clauseBuilder.length());
clauseBuilder.append(BRACKET_END);

} else if (clause instanceof Or) {
clauseBuilder.append(BRACKET_START);
Or or = (Or)clause;
Expand Down Expand Up @@ -133,7 +137,6 @@ private String buildInCondition(In in) {
stringBuilder.append(VALUE);
stringBuilder.append(COMMA);
}

stringBuilder.delete(stringBuilder.length() - 1, stringBuilder.length());
}

Expand Down Expand Up @@ -241,52 +244,60 @@ public String getLimit(Limit limit) {
}

public String getJoinStatement(Join[] joins, Resolver resolver) {
StringBuilder stringBuilder = new StringBuilder();
StringBuilder sb = new StringBuilder();

for (Join join : joins) {
SQLQuery sqlQuery = resolver.getSQLQuery(join.getTable());
String tableAliasName = join.getTableAliasName();

String tableName = sqlQuery.getTableName();

stringBuilder
.append(" ")
.append(getJoinType(join))
.append(" ")
.append(tableName)
.append(" ")
.append(getClause(join.getClauses()));
sb.append(" ")
.append(getJoinType(join))
.append(" ")
.append(tableName);

if (tableAliasName != null && tableAliasName.length() > 0)
{
sb.append(" AS ")
.append(tableAliasName);
}

sb.append(" ")
.append(getClause(join.getClauses()));

if (join.getJoin() != null) {
stringBuilder.append(" ")
.append(getJoinStatement(new Join[] { join.getJoin() }, resolver));
sb.append(" ")
.append(getJoinStatement(new Join[] { join.getJoin() }, resolver));
}
}

return stringBuilder.toString();
return sb.toString();
}

public String buildJoinQuery(String[] tableColumns, Join[] joins, String tableName, Clause[] clause,
public String buildJoinQuery(String[] tableColumns, Join[] joins, String tableName, String tableAlias, Clause[] clause,
OrderBy orderBy, Limit limit, Resolver resolver) {

String[] joinColumns = getJoinColumns(joins, resolver);

StringBuilder stringBuilder = new StringBuilder();
StringBuilder sb = new StringBuilder();

stringBuilder.append("SELECT ");
sb.append("SELECT ");

for (String column : tableColumns) {
stringBuilder.append(column).append(", ");
sb.append(column).append(", ");
}

for (String column : joinColumns) {
String columnAlias = column.replace(".","_");
stringBuilder.append(column)
sb.append(column)
.append(" as ")
.append(columnAlias)
.append(", ");
}

// remove the trailing comma
stringBuilder.delete(stringBuilder.length()-2, stringBuilder.length());
sb.delete(sb.length()-2, sb.length());

String clauseString = getClause(clause);
if (clauseString != null && clauseString.length() > 0) {
Expand All @@ -303,9 +314,14 @@ public String buildJoinQuery(String[] tableColumns, Join[] joins, String tableNa
limitString = "LIMIT " + limitString;
}

stringBuilder.append(" FROM ")
.append(tableName)
.append(" ")
sb.append(" FROM ")
.append(tableName);
if (tableAlias != null && tableAlias.length() > 0)
{
sb.append(" AS ")
.append(tableAlias);
}
sb.append(" ")
.append(getJoinStatement(joins, resolver))
.append(" ")
.append(clauseString)
Expand All @@ -314,7 +330,7 @@ public String buildJoinQuery(String[] tableColumns, Join[] joins, String tableNa
.append(" ")
.append(limitString);

return stringBuilder.toString();
return sb.toString();
}

private String[] getJoinColumns(Join[] joins, Resolver resolver) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ protected static Cursor selectCursor(Select select, Class<?> classDef, SQLProvid

return sqlProvider.query(
sqlQuery.getTableName(),
select.getTableAliasName(),
sqlQuery.getColumnNames(),
select.getClause(),
select.getJoin(),
Expand Down
Loading