[
  {
    "path": ".buildscript/deploy_snapshot.sh",
    "content": "#!/bin/bash\n#\n# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.\n#\n# Adapted from https://coderwall.com/p/9b_lfq and\n# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/\n\nSLUG=\"square/sqlbrite\"\nJDK=\"oraclejdk8\"\nBRANCH=\"master\"\n\nset -e\n\nif [ \"$TRAVIS_REPO_SLUG\" != \"$SLUG\" ]; then\n  echo \"Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'.\"\nelif [ \"$TRAVIS_JDK_VERSION\" != \"$JDK\" ]; then\n  echo \"Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'.\"\nelif [ \"$TRAVIS_PULL_REQUEST\" != \"false\" ]; then\n  echo \"Skipping snapshot deployment: was pull request.\"\nelif [ \"$TRAVIS_BRANCH\" != \"$BRANCH\" ]; then\n  echo \"Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'.\"\nelse\n  echo \"Deploying snapshot...\"\n  ./gradlew clean uploadArchives\n  echo \"Snapshot deployed!\"\nfi\n"
  },
  {
    "path": ".gitignore",
    "content": "# IntelliJ IDEA\n.idea\n*.iml\n\n# Gradle\n.gradle\ngradlew.bat\nbuild\nlocal.properties\nreports\n\n# Apple\n.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\n\nandroid:\n  components:\n    - tools\n    - platform-tools\n\njdk:\n  - oraclejdk8\n\nbefore_install:\n  # Install SDK license so Android Gradle plugin can install deps.\n  - mkdir \"$ANDROID_HOME/licenses\" || true\n  - echo \"d56f5187479451eabf01fb78af6dfcb131a6481e\" > \"$ANDROID_HOME/licenses/android-sdk-license\"\n  # Install the rest of tools (e.g., avdmanager)\n  - sdkmanager tools\n  # Install the system image\n  - sdkmanager \"system-images;android-18;default;armeabi-v7a\"\n  # Create and start emulator for the script. Meant to race the install task.\n  - echo no | avdmanager create avd --force -n test -k \"system-images;android-18;default;armeabi-v7a\"\n  - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window &\n\ninstall: ./gradlew clean assemble assembleAndroidTest --stacktrace\n\nbefore_script:\n  - android-wait-for-emulator\n  - adb shell input keyevent 82\n\nscript: ./gradlew check connectedCheck --stacktrace\n\nafter_success:\n  - .buildscript/deploy_snapshot.sh\n\nenv:\n  global:\n    - secure: \"NIWC0zkThskXn7uduTJ1yT78voqEgzEfw8tOImGNBjZ/NDU6yxM4bh+tq+fnkn5ENjELV6fgcYd2DUJSWmkFD2k9ZMRNLm//AqlQihl8aT+DpWhDdCkQjnolHnjm1O7+ys7Q/vswBZEzkBxzIgivajZEzvjarQItJjbpBftQ0Cs=\"\n    - secure: \"ahPT9EzJVpkM4q2HA/VBxUzgicvfdOOZaEvOiQKJofy1FrLjrBS2LFxqCbyffg0sjGUyvBMLg767CSt/0xRRFWIpsjxCfmvEmAURi89zdZ8MUNXIwe7x/0lXCdQIt8eueq3Qh5qFwJUy4aFbzVvcmMXKswWzw1O0+IcvYX00/xc=\"\n\nbranches:\n  except:\n    - gh-pages\n\nnotifications:\n  email: false\n\nsudo: false\n\ncache:\n  directories:\n    - $HOME/.gradle\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Change Log\n==========\n\nVersion 3.2.0 *(2018-03-05)*\n----------------------------\n\n * New: Add `query(SupportSQLiteQuery)` method for one-off queries.\n\n\nVersion 3.1.1 *(2018-02-12)*\n----------------------------\n\n * Fix: Useless `BuildConfig` classes are no longer included.\n * Fix: Eliminate Java interop checks for Kotlin extensions as they're only for Kotlin consumers and the checks exist in the Java code they delegate to anyway.\n\n\nVersion 3.1.0 *(2017-12-18)*\n----------------------------\n\n * New: `inTransaction` Kotlin extension function which handles starting, marking successful, and ending\n   a transaction.\n * New: Embedded lint check which validates the number of arguments passed to `query` and `createQuery`\n   match the number of expected arguments of the SQL statement.\n * Fix: Properly indent multi-line SQL statements in the logs for `query`.\n\n\nVersion 3.0.0 *(2017-11-28)*\n----------------------------\n\nGroup ID has changed to `com.squareup.sqlbrite3`.\n\n * New: Build on top of the Android architecture components Sqlite support library. This allows swapping\n   out the underlying Sqlite implementation to that of your choosing.\n\nBecause of the way the Sqlite support library works, there is no interop bridge between 1.x or 2.x to\nthis new version. If you haven't fully migrated to 2.x, complete that migration first and then upgrade\nto 3.x all at once.\n\n\nVersion 2.0.0 *(2017-07-07)*\n----------------------------\n\nGroup ID has changed to `com.squareup.sqlbrite2`.\n\n * New: RxJava 2.x support. Backpressure is no longer supported as evidenced by the use of\n   `Observable`. If you want to slow down query notifications based on backpressure or another metric\n   like time then you should apply those operators yourself.\n * New: `mapToOptional` for queries that return 0 or 1 rows.\n * New: `sqlbrite-kotlin` module provides `mapTo*` extension functions for `Observable<Query>`.\n * New: `sqlbrite-interop` module allows bridging 1.x and 2.x libraries together so that notifications\n   from each trigger queries from the other.\n\nNote: This version only supports RxJava 2.\n\n\nVersion 1.1.2 *(2017-06-30)*\n----------------------------\n\n * Internal architecture changes to support the upcoming 2.0 release and a bridge allowing both 1.x\n   and 2.x to be used at the same time.\n\n\nVersion 1.1.1 *(2016-12-20)*\n----------------------------\n\n * Fix: Correct spelling of `getWritableDatabase()` to match `SQLiteOpenHelper`.\n\n\nVersion 1.1.0 *(2016-12-16)*\n----------------------------\n\n * New: Expose `getReadableDatabase()` and `getWriteableDatabase()` convenience methods.\n * Fix: Do not cache instances of the readable and writable database internally as the framework\n   does this by default.\n\n\nVersion 1.0.0 *(2016-12-02)*\n----------------------------\n\n * RxJava dependency updated to 1.2.3.\n * Restore `@WorkerThread` annotations to methods which do I/O. If you're using Java 8 with\n   Retrolambda or Jack you need to use version 2.3 or newer of the Android Gradle plugin to have\n   these annotations correctly handled by lint.\n\n\nVersion 0.8.0 *(2016-10-21)*\n----------------------------\n\n * New: A `Transformer<Query, Query>` can be supplied which is applied to each returned observable.\n * New: `newNonExclusiveTransaction()` starts transactions in `IMMEDIATE` mode. See the platform\n   or SQLite documentation for more information.\n * New: APIs for insert/update/delete which allow providing a compiled `SQLiteStatement`.\n\n\nVersion 0.7.0 *(2016-07-06)*\n----------------------------\n\n * New: Allow `mapTo*` mappers to return `null` values. This is useful when querying on a single,\n   nullable column for which `null` is a valid value.\n * Fix: When `mapToOne` does not emit a value downstream, request another value from upstream to\n   ensure fixed-item requests (such as `take(1)`) as properly honored.\n * Fix: Add logging to synchronous `execute` methods.\n\n\nVersion 0.6.3 *(2016-04-13)*\n----------------------------\n\n * `QueryObservable` constructor is now public allow instances to be created for tests.\n\n\nVersion 0.6.2 *(2016-03-01)*\n----------------------------\n\n * Fix: Document explicitly and correctly handle the fact that `Query.run()` can return `null` in\n   some situations. The `mapToOne`, `mapToOneOrDefault`, `mapToList`, and `asRows` helpers have all\n   been updated to handle this case and each is documented with their respective behavior.\n\n\nVersion 0.6.1 *(2016-02-29)*\n----------------------------\n\n * Fix: Apply backpressure strategy between database/content provider and the supplied `Scheduler`.\n   This guards against backpressure exceptions when the scheduler is unable to keep up with the rate\n   at which queries are being triggered.\n * Fix: Indent the subsequent lines of a multi-line queries when logging.\n\n\nVersion 0.6.0 *(2016-02-17)*\n----------------------------\n\n * New: Require a `Scheduler` when wrapping a database or content provider which will be used when\n   sending query triggers. This allows the query to be run in subsequent operators without needing an\n   additional `observeOn`. It also eliminates the need to use `subscribeOn` since the supplied\n   `Scheduler` will be used for all emissions (similar to RxJava's `timer`, `interval`, etc.).\n\n   This also corrects a potential violation of the RxJava contract and potential source of bugs in that\n   all triggers will occur on the supplied `Scheduler`. Previously the initial value would trigger\n   synchronously (on the subscribing thread) while subsequent ones trigger on the thread which\n   performed the transaction. The new behavior puts the initial trigger on the same thread as all\n   subsequent triggers and also does not force transactions to block while sending triggers.\n\n\nVersion 0.5.1 *(2016-02-03)*\n----------------------------\n\n * New: Query logs now contain timing information on how long they took to execute. This only covers\n   the time until a `Cursor` was made available, not object mapping or delivering to subscribers.\n * Fix: Switch query logging to happen when `Query.run` is called, not when a query is triggered.\n * Fix: Check for subscribing inside a transaction using a more accurate primitive.\n\n\nVersion 0.5.0 *(2015-12-09)*\n----------------------------\n\n * New: Expose `mapToOne`, `mapToOneOrDefault`, and `mapToList` as static methods on `Query`. These\n   mirror the behavior of the methods of the same name on `QueryObservable` but can be used later in\n   a stream by passing the returned `Operator` instances to `lift()` (e.g.,\n   `take(1).lift(Query.mapToOne(..))`).\n * Requires RxJava 1.1.0 or newer.\n\n\nVersion 0.4.1 *(2015-10-19)*\n----------------------------\n\n * New: `execute` method provides the ability to execute arbitrary SQL statements.\n * New: `executeAndTrigger` method provides the ability to execute arbitrary SQL statements and\n   notifying any queries to update on the specified table.\n * Fix: `Query.asRows` no longer calls `onCompleted` when the downstream subscriber has unsubscribed.\n\n\nVersion 0.4.0 *(2015-09-22)*\n----------------------------\n\n * New: `mapToOneOrDefault` replaces `mapToOneOrNull` for more flexibility.\n * Fix: Notifications of table updates as the result of a transaction now occur after the transaction\n   has been applied. Previous the notification would happen during the commit at which time it was\n   invalid to create a new transaction in a subscriber.\n\n\nVersion 0.3.1 *(2015-09-02)*\n----------------------------\n\n * New: `mapToOne` and `mapToOneOrNull` operators on `QueryObservable`. These work on queries which\n   return 0 or 1 rows and are a convenience for turning them into a type `T` given a mapper of type\n   `Func1<Cursor, T>` (the same which can be used for `mapToList`).\n * Fix: Remove `@WorkerThread` annotations for now. Various combinations of lint, RxJava, and\n   retrolambda can cause false-positives.\n\n\nVersion 0.3.0 *(2015-08-31)*\n----------------------------\n\n * Transactions are now exposed as objects instead of methods. Call `newTransaction()` to start a\n   transaction. On the `Transaction` instance, call `markSuccessful()` to indicate success and\n   `end()` to commit or rollback the transaction. The `Transaction` instance implements `Closeable`\n   to allow its use in a try-with-resources construct. See the `newTransaction()` Javadoc for more\n   information.\n * `Query` instances can now be turned directly into an `Observable<T>` by calling `asRows` with a\n   `Func1<Cursor, T>` that maps rows to a type `T`. This allows easy filtering and limiting in\n   memory rather than in the query. See the `asRows` Javadoc for more information.\n * `createQuery` now returns a `QueryObservable` which offers a `mapToList` operator. This operator\n   also takes a `Func1<Cursor, T>` for mapping rows to a type `T`, but instead of individual rows it\n   collects all the rows into a list. For large query results or frequently updated tables this can\n   create a lot of objects. See the `mapToList` Javadoc for more information.\n * New: Nullability, `@CheckResult`, and `@WorkerThread` annotations on all APIs allow a more useful\n   interaction with lint in consuming projects.\n\n\nVersion 0.2.1 *(2015-07-14)*\n----------------------------\n\n * Fix: Add support for backpressure.\n\n\nVersion 0.2.0 *(2015-06-30)*\n----------------------------\n\n * An `Observable<Query>` can now be created from wrapping a `ContentResolver` in order to observe\n   queries from another app's content provider.\n * `SqlBrite` class is now a factory for both a `BriteDatabase` (the `SQLiteOpenHelper` wrapper)\n   and `BriteContentResolver` (the `ContentResolver` wrapper).\n\n\nVersion 0.1.0 *(2015-02-21)*\n----------------------------\n\nInitial release.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing\n============\n\nIf you would like to contribute code you can do so through GitHub by forking\nthe repository and sending a pull request.\n\nWhen submitting code, please make every effort to follow existing conventions\nand style in order to keep the code as readable as possible. Please also make\nsure your code compiles by running `./gradlew clean build`.\n\nBefore your code can be accepted into the project you must also sign the\n[Individual Contributor License Agreement (CLA)][1].\n\n\n [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "SQL Brite\n=========\n\nA lightweight wrapper around `SupportSQLiteOpenHelper` and `ContentResolver` which introduces reactive\nstream semantics to queries.\n\n# Deprecated\n\nThis library is no longer actively developed and is considered complete.\n\nIts database features (and far, far more) are now offered by [SQLDelight](https://github.com/cashapp/sqldelight/)\nand its [upgrading guide](https://github.com/cashapp/sqldelight/blob/1.0.0/UPGRADING.md) offers some\nmigration help.\n\nFor content provider monitoring please use [Copper](https://github.com/cashapp/copper) instead.\n\n\n\nUsage\n-----\n\nCreate a `SqlBrite` instance which is an adapter for the library functionality.\n\n```java\nSqlBrite sqlBrite = new SqlBrite.Builder().build();\n```\n\nPass a `SupportSQLiteOpenHelper` instance and a `Scheduler` to create a `BriteDatabase`.\n\n```java\nBriteDatabase db = sqlBrite.wrapDatabaseHelper(openHelper, Schedulers.io());\n```\n\nA `Scheduler` is required for a few reasons, but the most important is that query notifications can\ntrigger on the thread of your choice. The query can then be run without blocking the main thread or\nthe thread which caused the trigger.\n\nThe `BriteDatabase.createQuery` method is similar to `SupportSQLiteDatabase.query` except it takes an\nadditional parameter of table(s) on which to listen for changes. Subscribe to the returned\n`Observable<Query>` which will immediately notify with a `Query` to run.\n\n```java\nObservable<Query> users = db.createQuery(\"users\", \"SELECT * FROM users\");\nusers.subscribe(new Consumer<Query>() {\n  @Override public void accept(Query query) {\n    Cursor cursor = query.run();\n    // TODO parse data...\n  }\n});\n```\n\nUnlike a traditional `query`, updates to the specified table(s) will trigger additional\nnotifications for as long as you remain subscribed to the observable. This means that when you\ninsert, update, or delete data, any subscribed queries will update with the new data instantly.\n\n```java\nfinal AtomicInteger queries = new AtomicInteger();\nusers.subscribe(new Consumer<Query>() {\n  @Override public void accept(Query query) {\n    queries.getAndIncrement();\n  }\n});\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 1\n\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"jw\", \"Jake Wharton\"));\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"mattp\", \"Matt Precious\"));\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"strong\", \"Alec Strong\"));\n\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 4\n```\n\nIn the previous example we re-used the `BriteDatabase` object \"db\" for inserts. All insert, update,\nor delete operations must go through this object in order to correctly notify subscribers.\n\nUnsubscribe from the returned `Subscription` to stop getting updates.\n\n```java\nfinal AtomicInteger queries = new AtomicInteger();\nSubscription s = users.subscribe(new Consumer<Query>() {\n  @Override public void accept(Query query) {\n    queries.getAndIncrement();\n  }\n});\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 1\n\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"jw\", \"Jake Wharton\"));\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"mattp\", \"Matt Precious\"));\ns.unsubscribe();\n\ndb.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"strong\", \"Alec Strong\"));\n\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 3\n```\n\nUse transactions to prevent large changes to the data from spamming your subscribers.\n\n```java\nfinal AtomicInteger queries = new AtomicInteger();\nusers.subscribe(new Consumer<Query>() {\n  @Override public void accept(Query query) {\n    queries.getAndIncrement();\n  }\n});\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 1\n\nTransaction transaction = db.newTransaction();\ntry {\n  db.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"jw\", \"Jake Wharton\"));\n  db.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"mattp\", \"Matt Precious\"));\n  db.insert(\"users\", SQLiteDatabase.CONFLICT_ABORT, createUser(\"strong\", \"Alec Strong\"));\n  transaction.markSuccessful();\n} finally {\n  transaction.end();\n}\n\nSystem.out.println(\"Queries: \" + queries.get()); // Prints 2\n```\n*Note: You can also use try-with-resources with a `Transaction` instance.*\n\nSince queries are just regular RxJava `Observable` objects, operators can also be used to\ncontrol the frequency of notifications to subscribers.\n\n```java\nusers.debounce(500, MILLISECONDS).subscribe(new Consumer<Query>() {\n  @Override public void accept(Query query) {\n    // TODO...\n  }\n});\n```\n\nThe `SqlBrite` object can also wrap a `ContentResolver` for observing a query on another app's\ncontent provider.\n\n```java\nBriteContentResolver resolver = sqlBrite.wrapContentProvider(contentResolver, Schedulers.io());\nObservable<Query> query = resolver.createQuery(/*...*/);\n```\n\nThe full power of RxJava's operators are available for combining, filtering, and triggering any\nnumber of queries and data changes.\n\n\n\nPhilosophy\n----------\n\nSQL Brite's only responsibility is to be a mechanism for coordinating and composing the notification\nof updates to tables such that you can update queries as soon as data changes.\n\nThis library is not an ORM. It is not a type-safe query mechanism. It won't serialize the same POJOs\nyou use for Gson. It's not going to perform database migrations for you.\n\nSome of these features are offered by [SQL Delight][sqldelight] which can be used with SQL Brite.\n\n\n\nDownload\n--------\n\n```groovy\nimplementation 'com.squareup.sqlbrite3:sqlbrite:3.2.0'\n```\n\nFor the 'kotlin' module that adds extension functions to `Observable<Query>`:\n```groovy\nimplementation 'com.squareup.sqlbrite3:sqlbrite-kotlin:3.2.0'\n```\n\n\nSnapshots of the development version are available in [Sonatype's `snapshots` repository][snap].\n\n\n\nLicense\n-------\n\n    Copyright 2015 Square, Inc.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n\n\n\n\n [snap]: https://oss.sonatype.org/content/repositories/snapshots/\n [sqldelight]: https://github.com/square/sqldelight/\n"
  },
  {
    "path": "RELEASING.md",
    "content": "Releasing\n========\n\n 1. Change the version in `gradle.properties` to a non-SNAPSHOT version.\n 2. Update the `CHANGELOG.md` for the impending release.\n 3. Update the `README.md` with the new version.\n 4. `git commit -am \"Prepare for release X.Y.Z.\"` (where X.Y.Z is the new version)\n 5. `./gradlew clean uploadArchives`.\n 6. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact.\n 7. `git tag -a X.Y.Z -m \"Version X.Y.Z\"` (where X.Y.Z is the new version)\n 8. Update the `gradle.properties` to the next SNAPSHOT version.\n 9. `git commit -am \"Prepare next development version.\"`\n 10. `git push && git push --tags`\n\nIf step 5 or 6 fails, drop the Sonatype repo, fix the problem, commit, and start again at step 5.\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n  ext.versions = [\n      'minSdk': 14,\n      'compileSdk': 27,\n      'kotlin': '1.1.60',\n      'lint': '26.0.1'\n  ]\n\n  repositories {\n    mavenCentral()\n    google()\n    jcenter()\n  }\n\n  dependencies {\n    classpath 'com.android.tools.build:gradle:3.0.1'\n    classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}\"\n  }\n}\n\nallprojects {\n  repositories {\n    mavenCentral()\n    google()\n    jcenter()\n  }\n\n  group = GROUP\n  version = VERSION_NAME\n}\n\next {\n  // Android dependencies.\n  supportV4 = 'com.android.support:support-v4:27.0.0'\n  supportAnnotations = 'com.android.support:support-annotations:27.0.0'\n  supportTestRunner = 'com.android.support.test:runner:0.5'\n\n  supportSqlite = 'android.arch.persistence:db:1.0.0'\n  supportSqliteFramework = 'android.arch.persistence:db-framework:1.0.0'\n\n  // Third-party dependencies.\n  kotlinStdLib = \"org.jetbrains.kotlin:kotlin-stdlib:${versions.kotlin}\"\n  dagger = 'com.google.dagger:dagger:2.13'\n  daggerCompiler = 'com.google.dagger:dagger-compiler:2.13'\n  butterKnifeRuntime = 'com.jakewharton:butterknife:8.8.1'\n  butterKnifeCompiler = 'com.jakewharton:butterknife-compiler:8.8.1'\n  timber = 'com.jakewharton.timber:timber:4.6.0'\n  autoValue = 'com.google.auto.value:auto-value:1.5'\n  autoValueParcel = 'com.ryanharter.auto.value:auto-value-parcel:0.2.5'\n  rxJava = 'io.reactivex.rxjava2:rxjava:2.1.3'\n  rxAndroid = 'io.reactivex.rxjava2:rxandroid:2.0.1'\n  rxBinding = 'com.jakewharton.rxbinding2:rxbinding:2.0.0'\n  junit = 'junit:junit:4.12'\n  truth = 'com.google.truth:truth:0.36'\n\n  // Lint dependencies.\n  lintApi = \"com.android.tools.lint:lint-api:${versions.lint}\"\n  lint = \"com.android.tools.lint:lint:${versions.lint}\"\n  lintTests = \"com.android.tools.lint:lint-tests:${versions.lint}\"\n}\n\nconfigurations {\n  osstrich\n}\ndependencies {\n  osstrich 'com.squareup.osstrich:osstrich:1.2.0'\n}\ntask publishV1Javadoc(type: JavaExec) {\n  classpath = configurations.osstrich\n  main = 'com.squareup.osstrich.JavadocPublisher'\n  args = [\n      'build/javadoc',\n      'https://github.com/square/sqlbrite',\n      'com.squareup.sqlbrite'\n  ]\n}\ntask publishV2Javadoc(type: JavaExec) {\n  classpath = configurations.osstrich\n  main = 'com.squareup.osstrich.JavadocPublisher'\n  args = [\n      'build/javadoc',\n      'https://github.com/square/sqlbrite',\n      'com.squareup.sqlbrite2'\n  ]\n}\ntask publishV3Javadoc(type: JavaExec) {\n  classpath = configurations.osstrich\n  main = 'com.squareup.osstrich.JavadocPublisher'\n  args = [\n      'build/javadoc',\n      'https://github.com/square/sqlbrite',\n      'com.squareup.sqlbrite3'\n  ]\n}\ntask publishJavadoc(dependsOn: [publishV1Javadoc, publishV2Javadoc, publishV3Javadoc])\n"
  },
  {
    "path": "gradle/gradle-mvn-push.gradle",
    "content": "/*\n * Copyright 2013 Chris Banes\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\napply plugin: 'maven'\napply plugin: 'signing'\n\ndef isReleaseBuild() {\n    return VERSION_NAME.contains(\"SNAPSHOT\") == false\n}\n\ndef getRepositoryUsername() {\n    return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : \"\"\n}\n\ndef getRepositoryPassword() {\n    return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : \"\"\n}\n\nafterEvaluate { project ->\n    uploadArchives {\n        repositories {\n            mavenDeployer {\n                beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }\n\n                pom.groupId = GROUP\n                pom.artifactId = POM_ARTIFACT_ID\n                pom.version = VERSION_NAME\n\n                repository(url: \"https://oss.sonatype.org/service/local/staging/deploy/maven2/\") {\n                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n                }\n                snapshotRepository(url: \"https://oss.sonatype.org/content/repositories/snapshots/\") {\n                    authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())\n                }\n\n                pom.project {\n                    name POM_NAME\n                    packaging POM_PACKAGING\n                    description POM_DESCRIPTION\n                    url POM_URL\n\n                    scm {\n                        url POM_SCM_URL\n                        connection POM_SCM_CONNECTION\n                        developerConnection POM_SCM_DEV_CONNECTION\n                    }\n\n                    licenses {\n                        license {\n                            name POM_LICENCE_NAME\n                            url POM_LICENCE_URL\n                            distribution POM_LICENCE_DIST\n                        }\n                    }\n\n                    developers {\n                        developer {\n                            id POM_DEVELOPER_ID\n                            name POM_DEVELOPER_NAME\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    signing {\n        required { isReleaseBuild() && gradle.taskGraph.hasTask(\"uploadArchives\") }\n        sign configurations.archives\n    }\n\n    task androidJavadocs(type: Javadoc) {\n        if (!project.plugins.hasPlugin('kotlin-android')) {\n            source = android.sourceSets.main.java.srcDirs\n        }\n        classpath += project.files(android.getBootClasspath().join(File.pathSeparator))\n\n        if (JavaVersion.current().isJava8Compatible()) {\n            options.addStringOption('Xdoclint:none', '-quiet')\n        }\n    }\n\n    task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {\n        classifier = 'javadoc'\n        from androidJavadocs.destinationDir\n    }\n\n    task androidSourcesJar(type: Jar) {\n        classifier = 'sources'\n        from android.sourceSets.main.java.sourceFiles\n    }\n\n    artifacts {\n        archives androidSourcesJar\n        archives androidJavadocsJar\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.4-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "GROUP=com.squareup.sqlbrite3\nVERSION_NAME=3.2.1-SNAPSHOT\n\nPOM_DESCRIPTION=A lightweight wrapper around SQLiteOpenHelper which introduces reactive stream semantics to SQL operations.\n\nPOM_URL=http://github.com/square/sqlbrite/\nPOM_SCM_URL=http://github.com/square/sqlbrite/\nPOM_SCM_CONNECTION=scm:git:git://github.com/square/sqlbrite.git\nPOM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/square/sqlbrite.git\n\nPOM_LICENCE_NAME=The Apache Software License, Version 2.0\nPOM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt\nPOM_LICENCE_DIST=repo\n\nPOM_DEVELOPER_ID=square\nPOM_DEVELOPER_NAME=Square, Inc.\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "sample/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\ndependencies {\n  implementation rootProject.ext.supportV4\n  implementation rootProject.ext.supportAnnotations\n\n  implementation rootProject.ext.dagger\n  annotationProcessor rootProject.ext.daggerCompiler\n\n  implementation rootProject.ext.butterKnifeRuntime\n  annotationProcessor rootProject.ext.butterKnifeCompiler\n  implementation rootProject.ext.timber\n  implementation rootProject.ext.rxJava\n  implementation rootProject.ext.rxAndroid\n  implementation rootProject.ext.rxBinding\n\n  compileOnly rootProject.ext.autoValue\n  annotationProcessor rootProject.ext.autoValue\n  annotationProcessor rootProject.ext.autoValueParcel\n\n  implementation project(':sqlbrite')\n  implementation rootProject.ext.supportSqliteFramework\n}\n\nandroid {\n  compileSdkVersion versions.compileSdk\n\n  compileOptions {\n    sourceCompatibility JavaVersion.VERSION_1_7\n    targetCompatibility JavaVersion.VERSION_1_7\n  }\n\n  lintOptions {\n    textOutput 'stdout'\n    textReport true\n    ignore 'InvalidPackage' // Provided AutoValue pulls in Guava and friends. Doesn't end up in APK.\n  }\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n    targetSdkVersion versions.compileSdk\n    applicationId 'com.example.sqlbrite.todo'\n\n    versionCode 1\n    versionName '1.0'\n  }\n\n  signingConfigs {\n    debug {\n      storeFile file('debug.keystore')\n      storePassword 'android'\n      keyAlias 'android'\n      keyPassword 'android'\n    }\n  }\n\n  buildTypes {\n    debug {\n      applicationIdSuffix '.development'\n      signingConfig signingConfigs.debug\n    }\n  }\n}\n"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.example.sqlbrite.todo\"\n    >\n\n  <application\n      android:label=\"@string/app_name\"\n      android:name=\".TodoApp\"\n      android:allowBackup=\"false\"\n      android:icon=\"@drawable/ic_launcher\"\n      >\n    <activity\n        android:name=\".ui.MainActivity\"\n        >\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\"/>\n\n        <category android:name=\"android.intent.category.LAUNCHER\"/>\n        <category android:name=\"android.intent.category.DEFAULT\"/>\n      </intent-filter>\n    </activity>\n\n  </application>\n</manifest>\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/TodoApp.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo;\n\nimport android.app.Application;\nimport android.content.Context;\nimport timber.log.Timber;\n\npublic final class TodoApp extends Application {\n  private TodoComponent mainComponent;\n\n  @Override public void onCreate() {\n    super.onCreate();\n\n    if (BuildConfig.DEBUG) {\n      Timber.plant(new Timber.DebugTree());\n    }\n\n    mainComponent = DaggerTodoComponent.builder().todoModule(new TodoModule(this)).build();\n  }\n\n  public static TodoComponent getComponent(Context context) {\n    return ((TodoApp) context.getApplicationContext()).mainComponent;\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/TodoComponent.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo;\n\nimport com.example.sqlbrite.todo.ui.ItemsFragment;\nimport com.example.sqlbrite.todo.ui.ListsFragment;\nimport com.example.sqlbrite.todo.ui.NewItemFragment;\nimport com.example.sqlbrite.todo.ui.NewListFragment;\nimport dagger.Component;\nimport javax.inject.Singleton;\n\n@Singleton\n@Component(modules = TodoModule.class)\npublic interface TodoComponent {\n\n  void inject(ListsFragment fragment);\n\n  void inject(ItemsFragment fragment);\n\n  void inject(NewItemFragment fragment);\n\n  void inject(NewListFragment fragment);\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/TodoModule.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo;\n\nimport android.app.Application;\nimport com.example.sqlbrite.todo.db.DbModule;\nimport dagger.Module;\nimport dagger.Provides;\nimport javax.inject.Singleton;\n\n@Module(\n    includes = {\n        DbModule.class,\n    }\n)\npublic final class TodoModule {\n  private final Application application;\n\n  TodoModule(Application application) {\n    this.application = application;\n  }\n\n  @Provides @Singleton Application provideApplication() {\n    return application;\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/db/Db.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.db;\n\nimport android.database.Cursor;\n\npublic final class Db {\n  public static final int BOOLEAN_FALSE = 0;\n  public static final int BOOLEAN_TRUE = 1;\n\n  public static String getString(Cursor cursor, String columnName) {\n    return cursor.getString(cursor.getColumnIndexOrThrow(columnName));\n  }\n\n  public static boolean getBoolean(Cursor cursor, String columnName) {\n    return getInt(cursor, columnName) == BOOLEAN_TRUE;\n  }\n\n  public static long getLong(Cursor cursor, String columnName) {\n    return cursor.getLong(cursor.getColumnIndexOrThrow(columnName));\n  }\n\n  public static int getInt(Cursor cursor, String columnName) {\n    return cursor.getInt(cursor.getColumnIndexOrThrow(columnName));\n  }\n\n  private Db() {\n    throw new AssertionError(\"No instances.\");\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/db/DbCallback.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.db;\n\nimport android.arch.persistence.db.SupportSQLiteDatabase;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.content.Context;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;\n\nfinal class DbCallback extends SupportSQLiteOpenHelper.Callback {\n  private static final int VERSION = 1;\n\n  private static final String CREATE_LIST = \"\"\n      + \"CREATE TABLE \" + TodoList.TABLE + \"(\"\n      + TodoList.ID + \" INTEGER NOT NULL PRIMARY KEY,\"\n      + TodoList.NAME + \" TEXT NOT NULL,\"\n      + TodoList.ARCHIVED + \" INTEGER NOT NULL DEFAULT 0\"\n      + \")\";\n  private static final String CREATE_ITEM = \"\"\n      + \"CREATE TABLE \" + TodoItem.TABLE + \"(\"\n      + TodoItem.ID + \" INTEGER NOT NULL PRIMARY KEY,\"\n      + TodoItem.LIST_ID + \" INTEGER NOT NULL REFERENCES \" + TodoList.TABLE + \"(\" + TodoList.ID + \"),\"\n      + TodoItem.DESCRIPTION + \" TEXT NOT NULL,\"\n      + TodoItem.COMPLETE + \" INTEGER NOT NULL DEFAULT 0\"\n      + \")\";\n  private static final String CREATE_ITEM_LIST_ID_INDEX =\n      \"CREATE INDEX item_list_id ON \" + TodoItem.TABLE + \" (\" + TodoItem.LIST_ID + \")\";\n\n  DbCallback() {\n    super(VERSION);\n  }\n\n  @Override public void onCreate(SupportSQLiteDatabase db) {\n    db.execSQL(CREATE_LIST);\n    db.execSQL(CREATE_ITEM);\n    db.execSQL(CREATE_ITEM_LIST_ID_INDEX);\n\n    long groceryListId = db.insert(TodoList.TABLE, CONFLICT_FAIL, new TodoList.Builder()\n        .name(\"Grocery List\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(groceryListId)\n        .description(\"Beer\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(groceryListId)\n        .description(\"Point Break on DVD\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(groceryListId)\n        .description(\"Bad Boys 2 on DVD\")\n        .build());\n\n    long holidayPresentsListId = db.insert(TodoList.TABLE, CONFLICT_FAIL, new TodoList.Builder()\n        .name(\"Holiday Presents\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(holidayPresentsListId)\n        .description(\"Pogo Stick for Jake W.\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(holidayPresentsListId)\n        .description(\"Jack-in-the-box for Alec S.\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(holidayPresentsListId)\n        .description(\"Pogs for Matt P.\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(holidayPresentsListId)\n        .description(\"Cola for Jesse W.\")\n        .build());\n\n    long workListId = db.insert(TodoList.TABLE, CONFLICT_FAIL, new TodoList.Builder()\n        .name(\"Work Items\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(workListId)\n        .description(\"Finish SqlBrite library\")\n        .complete(true)\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(workListId)\n        .description(\"Finish SqlBrite sample app\")\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder()\n        .listId(workListId)\n        .description(\"Publish SqlBrite to GitHub\")\n        .build());\n\n    long birthdayPresentsListId = db.insert(TodoList.TABLE, CONFLICT_FAIL, new TodoList.Builder()\n        .name(\"Birthday Presents\")\n        .archived(true)\n        .build());\n    db.insert(TodoItem.TABLE, CONFLICT_FAIL, new TodoItem.Builder().listId(birthdayPresentsListId)\n        .description(\"New car\")\n        .complete(true)\n        .build());\n  }\n\n  @Override public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/db/DbModule.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.db;\n\nimport android.app.Application;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Factory;\nimport android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;\nimport com.squareup.sqlbrite3.BriteDatabase;\nimport com.squareup.sqlbrite3.SqlBrite;\nimport dagger.Module;\nimport dagger.Provides;\nimport io.reactivex.schedulers.Schedulers;\nimport javax.inject.Singleton;\nimport timber.log.Timber;\n\n@Module\npublic final class DbModule {\n  @Provides @Singleton SqlBrite provideSqlBrite() {\n    return new SqlBrite.Builder()\n        .logger(new SqlBrite.Logger() {\n          @Override public void log(String message) {\n            Timber.tag(\"Database\").v(message);\n          }\n        })\n        .build();\n  }\n\n  @Provides @Singleton BriteDatabase provideDatabase(SqlBrite sqlBrite, Application application) {\n    Configuration configuration = Configuration.builder(application)\n        .name(\"todo.db\")\n        .callback(new DbCallback())\n        .build();\n    Factory factory = new FrameworkSQLiteOpenHelperFactory();\n    SupportSQLiteOpenHelper helper = factory.create(configuration);\n    BriteDatabase db = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());\n    db.setLoggingEnabled(true);\n    return db;\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/db/TodoItem.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.db;\n\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.os.Parcelable;\nimport com.google.auto.value.AutoValue;\nimport io.reactivex.functions.Function;\n\n@AutoValue\npublic abstract class TodoItem implements Parcelable {\n  public static final String TABLE = \"todo_item\";\n\n  public static final String ID = \"_id\";\n  public static final String LIST_ID = \"todo_list_id\";\n  public static final String DESCRIPTION = \"description\";\n  public static final String COMPLETE = \"complete\";\n\n  public abstract long id();\n  public abstract long listId();\n  public abstract String description();\n  public abstract boolean complete();\n\n  public static final Function<Cursor, TodoItem> MAPPER = new Function<Cursor, TodoItem>() {\n    @Override public TodoItem apply(Cursor cursor) {\n      long id = Db.getLong(cursor, ID);\n      long listId = Db.getLong(cursor, LIST_ID);\n      String description = Db.getString(cursor, DESCRIPTION);\n      boolean complete = Db.getBoolean(cursor, COMPLETE);\n      return new AutoValue_TodoItem(id, listId, description, complete);\n    }\n  };\n\n  public static final class Builder {\n    private final ContentValues values = new ContentValues();\n\n    public Builder id(long id) {\n      values.put(ID, id);\n      return this;\n    }\n\n    public Builder listId(long listId) {\n      values.put(LIST_ID, listId);\n      return this;\n    }\n\n    public Builder description(String description) {\n      values.put(DESCRIPTION, description);\n      return this;\n    }\n\n    public Builder complete(boolean complete) {\n      values.put(COMPLETE, complete ? Db.BOOLEAN_TRUE : Db.BOOLEAN_FALSE);\n      return this;\n    }\n\n    public ContentValues build() {\n      return values; // TODO defensive copy?\n    }\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/db/TodoList.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.db;\n\nimport android.content.ContentValues;\nimport android.os.Parcelable;\nimport com.google.auto.value.AutoValue;\n\n// Note: normally I wouldn't prefix table classes but I didn't want 'List' to be overloaded.\n@AutoValue\npublic abstract class TodoList implements Parcelable {\n  public static final String TABLE = \"todo_list\";\n\n  public static final String ID = \"_id\";\n  public static final String NAME = \"name\";\n  public static final String ARCHIVED = \"archived\";\n\n  public abstract long id();\n  public abstract String name();\n  public abstract boolean archived();\n\n  public static final class Builder {\n    private final ContentValues values = new ContentValues();\n\n    public Builder id(long id) {\n      values.put(ID, id);\n      return this;\n    }\n\n    public Builder name(String name) {\n      values.put(NAME, name);\n      return this;\n    }\n\n    public Builder archived(boolean archived) {\n      values.put(ARCHIVED, archived);\n      return this;\n    }\n\n    public ContentValues build() {\n      return values; // TODO defensive copy?\n    }\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/ItemsAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.content.Context;\nimport android.text.SpannableString;\nimport android.text.style.StrikethroughSpan;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.CheckedTextView;\nimport com.example.sqlbrite.todo.db.TodoItem;\nimport io.reactivex.functions.Consumer;\nimport java.util.Collections;\nimport java.util.List;\n\nfinal class ItemsAdapter extends BaseAdapter implements Consumer<List<TodoItem>> {\n  private final LayoutInflater inflater;\n\n  private List<TodoItem> items = Collections.emptyList();\n\n  public ItemsAdapter(Context context) {\n    inflater = LayoutInflater.from(context);\n  }\n\n  @Override public void accept(List<TodoItem> items) {\n    this.items = items;\n    notifyDataSetChanged();\n  }\n\n  @Override public int getCount() {\n    return items.size();\n  }\n\n  @Override public TodoItem getItem(int position) {\n    return items.get(position);\n  }\n\n  @Override public long getItemId(int position) {\n    return getItem(position).id();\n  }\n\n  @Override public boolean hasStableIds() {\n    return true;\n  }\n\n  @Override public View getView(int position, View convertView, ViewGroup parent) {\n    if (convertView == null) {\n      convertView = inflater.inflate(android.R.layout.simple_list_item_multiple_choice, parent, false);\n    }\n\n    TodoItem item = getItem(position);\n    CheckedTextView textView = (CheckedTextView) convertView;\n    textView.setChecked(item.complete());\n\n    CharSequence description = item.description();\n    if (item.complete()) {\n      SpannableString spannable = new SpannableString(description);\n      spannable.setSpan(new StrikethroughSpan(), 0, description.length(), 0);\n      description = spannable;\n    }\n\n    textView.setText(description);\n\n    return convertView;\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/ItemsFragment.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.app.Activity;\nimport android.database.Cursor;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.view.MenuItemCompat;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ListView;\nimport butterknife.BindView;\nimport butterknife.ButterKnife;\nimport com.example.sqlbrite.todo.R;\nimport com.example.sqlbrite.todo.TodoApp;\nimport com.example.sqlbrite.todo.db.Db;\nimport com.example.sqlbrite.todo.db.TodoItem;\nimport com.example.sqlbrite.todo.db.TodoList;\nimport com.jakewharton.rxbinding2.widget.AdapterViewItemClickEvent;\nimport com.jakewharton.rxbinding2.widget.RxAdapterView;\nimport com.squareup.sqlbrite3.BriteDatabase;\nimport io.reactivex.Observable;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport io.reactivex.disposables.CompositeDisposable;\nimport io.reactivex.functions.BiFunction;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.functions.Function;\nimport io.reactivex.schedulers.Schedulers;\nimport javax.inject.Inject;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_NONE;\nimport static android.support.v4.view.MenuItemCompat.SHOW_AS_ACTION_IF_ROOM;\nimport static android.support.v4.view.MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;\nimport static com.squareup.sqlbrite3.SqlBrite.Query;\n\npublic final class ItemsFragment extends Fragment {\n  private static final String KEY_LIST_ID = \"list_id\";\n  private static final String LIST_QUERY = \"SELECT * FROM \"\n      + TodoItem.TABLE\n      + \" WHERE \"\n      + TodoItem.LIST_ID\n      + \" = ? ORDER BY \"\n      + TodoItem.COMPLETE\n      + \" ASC\";\n  private static final String COUNT_QUERY = \"SELECT COUNT(*) FROM \"\n      + TodoItem.TABLE\n      + \" WHERE \"\n      + TodoItem.COMPLETE\n      + \" = \"\n      + Db.BOOLEAN_FALSE\n      + \" AND \"\n      + TodoItem.LIST_ID\n      + \" = ?\";\n  private static final String TITLE_QUERY =\n      \"SELECT \" + TodoList.NAME + \" FROM \" + TodoList.TABLE + \" WHERE \" + TodoList.ID + \" = ?\";\n\n  public interface Listener {\n    void onNewItemClicked(long listId);\n  }\n\n  public static ItemsFragment newInstance(long listId) {\n    Bundle arguments = new Bundle();\n    arguments.putLong(KEY_LIST_ID, listId);\n\n    ItemsFragment fragment = new ItemsFragment();\n    fragment.setArguments(arguments);\n    return fragment;\n  }\n\n  @Inject BriteDatabase db;\n\n  @BindView(android.R.id.list) ListView listView;\n  @BindView(android.R.id.empty) View emptyView;\n\n  private Listener listener;\n  private ItemsAdapter adapter;\n  private CompositeDisposable disposables;\n\n  private long getListId() {\n    return getArguments().getLong(KEY_LIST_ID);\n  }\n\n  @Override public void onAttach(Activity activity) {\n    if (!(activity instanceof Listener)) {\n      throw new IllegalStateException(\"Activity must implement fragment Listener.\");\n    }\n\n    super.onAttach(activity);\n    TodoApp.getComponent(activity).inject(this);\n    setHasOptionsMenu(true);\n\n    listener = (Listener) activity;\n    adapter = new ItemsAdapter(activity);\n  }\n\n  @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n    super.onCreateOptionsMenu(menu, inflater);\n\n    MenuItem item = menu.add(R.string.new_item)\n        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {\n          @Override public boolean onMenuItemClick(MenuItem item) {\n            listener.onNewItemClicked(getListId());\n            return true;\n          }\n        });\n    MenuItemCompat.setShowAsAction(item, SHOW_AS_ACTION_IF_ROOM | SHOW_AS_ACTION_WITH_TEXT);\n  }\n\n  @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n      @Nullable Bundle savedInstanceState) {\n    return inflater.inflate(R.layout.items, container, false);\n  }\n\n  @Override\n  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {\n    super.onViewCreated(view, savedInstanceState);\n    ButterKnife.bind(this, view);\n    listView.setEmptyView(emptyView);\n    listView.setAdapter(adapter);\n\n    RxAdapterView.itemClickEvents(listView) //\n        .observeOn(Schedulers.io())\n        .subscribe(new Consumer<AdapterViewItemClickEvent>() {\n          @Override public void accept(AdapterViewItemClickEvent event) {\n            boolean newValue = !adapter.getItem(event.position()).complete();\n            db.update(TodoItem.TABLE, CONFLICT_NONE,\n                new TodoItem.Builder().complete(newValue).build(), TodoItem.ID + \" = ?\",\n                String.valueOf(event.id()));\n          }\n        });\n  }\n\n  @Override public void onResume() {\n    super.onResume();\n    String listId = String.valueOf(getListId());\n\n    disposables = new CompositeDisposable();\n\n    Observable<Integer> itemCount = db.createQuery(TodoItem.TABLE, COUNT_QUERY, listId) //\n        .map(new Function<Query, Integer>() {\n          @Override public Integer apply(Query query) {\n            Cursor cursor = query.run();\n            try {\n              if (!cursor.moveToNext()) {\n                throw new AssertionError(\"No rows\");\n              }\n              return cursor.getInt(0);\n            } finally {\n              cursor.close();\n            }\n          }\n        });\n    Observable<String> listName =\n        db.createQuery(TodoList.TABLE, TITLE_QUERY, listId).map(new Function<Query, String>() {\n          @Override public String apply(Query query) {\n            Cursor cursor = query.run();\n            try {\n              if (!cursor.moveToNext()) {\n                throw new AssertionError(\"No rows\");\n              }\n              return cursor.getString(0);\n            } finally {\n              cursor.close();\n            }\n          }\n        });\n    disposables.add(\n        Observable.combineLatest(listName, itemCount, new BiFunction<String, Integer, String>() {\n          @Override public String apply(String listName, Integer itemCount) {\n            return listName + \" (\" + itemCount + \")\";\n          }\n        })\n            .observeOn(AndroidSchedulers.mainThread())\n            .subscribe(new Consumer<String>() {\n              @Override public void accept(String title) throws Exception {\n                getActivity().setTitle(title);\n              }\n            }));\n\n    disposables.add(db.createQuery(TodoItem.TABLE, LIST_QUERY, listId)\n        .mapToList(TodoItem.MAPPER)\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(adapter));\n  }\n\n  @Override public void onPause() {\n    super.onPause();\n    disposables.dispose();\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/ListsAdapter.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.content.Context;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.BaseAdapter;\nimport android.widget.TextView;\nimport io.reactivex.functions.Consumer;\nimport java.util.Collections;\nimport java.util.List;\n\nfinal class ListsAdapter extends BaseAdapter implements Consumer<List<ListsItem>> {\n  private final LayoutInflater inflater;\n\n  private List<ListsItem> items = Collections.emptyList();\n\n  public ListsAdapter(Context context) {\n    this.inflater = LayoutInflater.from(context);\n  }\n\n  @Override public void accept(List<ListsItem> items) {\n    this.items = items;\n    notifyDataSetChanged();\n  }\n\n  @Override public int getCount() {\n    return items.size();\n  }\n\n  @Override public ListsItem getItem(int position) {\n    return items.get(position);\n  }\n\n  @Override public long getItemId(int position) {\n    return getItem(position).id();\n  }\n\n  @Override public boolean hasStableIds() {\n    return true;\n  }\n\n  @Override public View getView(int position, View convertView, ViewGroup parent) {\n    if (convertView == null) {\n      convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);\n    }\n\n    ListsItem item = getItem(position);\n    ((TextView) convertView).setText(item.name() + \" (\" + item.itemCount() + \")\");\n\n    return convertView;\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/ListsFragment.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.app.Activity;\nimport android.os.Bundle;\nimport android.support.annotation.Nullable;\nimport android.support.v4.app.Fragment;\nimport android.support.v4.view.MenuItemCompat;\nimport android.view.LayoutInflater;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.ListView;\nimport butterknife.BindView;\nimport butterknife.ButterKnife;\nimport butterknife.OnItemClick;\nimport com.example.sqlbrite.todo.R;\nimport com.example.sqlbrite.todo.TodoApp;\nimport com.squareup.sqlbrite3.BriteDatabase;\nimport io.reactivex.android.schedulers.AndroidSchedulers;\nimport io.reactivex.disposables.Disposable;\nimport javax.inject.Inject;\n\nimport static android.support.v4.view.MenuItemCompat.SHOW_AS_ACTION_IF_ROOM;\nimport static android.support.v4.view.MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT;\n\npublic final class ListsFragment extends Fragment {\n  interface Listener {\n    void onListClicked(long id);\n    void onNewListClicked();\n  }\n\n  static ListsFragment newInstance() {\n    return new ListsFragment();\n  }\n\n  @Inject BriteDatabase db;\n\n  @BindView(android.R.id.list) ListView listView;\n  @BindView(android.R.id.empty) View emptyView;\n\n  private Listener listener;\n  private ListsAdapter adapter;\n  private Disposable disposable;\n\n  @Override public void onAttach(Activity activity) {\n    if (!(activity instanceof Listener)) {\n      throw new IllegalStateException(\"Activity must implement fragment Listener.\");\n    }\n\n    super.onAttach(activity);\n    TodoApp.getComponent(activity).inject(this);\n    setHasOptionsMenu(true);\n\n    listener = (Listener) activity;\n    adapter = new ListsAdapter(activity);\n  }\n\n  @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {\n    super.onCreateOptionsMenu(menu, inflater);\n\n    MenuItem item = menu.add(R.string.new_list)\n        .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {\n          @Override public boolean onMenuItemClick(MenuItem item) {\n            listener.onNewListClicked();\n            return true;\n          }\n        });\n    MenuItemCompat.setShowAsAction(item, SHOW_AS_ACTION_IF_ROOM | SHOW_AS_ACTION_WITH_TEXT);\n  }\n\n  @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,\n      @Nullable Bundle savedInstanceState) {\n    return inflater.inflate(R.layout.lists, container, false);\n  }\n\n  @Override\n  public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {\n    super.onViewCreated(view, savedInstanceState);\n    ButterKnife.bind(this, view);\n    listView.setEmptyView(emptyView);\n    listView.setAdapter(adapter);\n  }\n\n  @OnItemClick(android.R.id.list) void listClicked(long listId) {\n    listener.onListClicked(listId);\n  }\n\n  @Override public void onResume() {\n    super.onResume();\n\n    getActivity().setTitle(\"To-Do\");\n\n    disposable = db.createQuery(ListsItem.TABLES, ListsItem.QUERY)\n        .mapToList(ListsItem.MAPPER)\n        .observeOn(AndroidSchedulers.mainThread())\n        .subscribe(adapter);\n  }\n\n  @Override public void onPause() {\n    super.onPause();\n    disposable.dispose();\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/ListsItem.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.database.Cursor;\nimport android.os.Parcelable;\nimport com.example.sqlbrite.todo.db.Db;\nimport com.example.sqlbrite.todo.db.TodoItem;\nimport com.example.sqlbrite.todo.db.TodoList;\nimport com.google.auto.value.AutoValue;\nimport io.reactivex.functions.Function;\nimport java.util.Arrays;\nimport java.util.Collection;\n\n@AutoValue\nabstract class ListsItem implements Parcelable {\n  private static String ALIAS_LIST = \"list\";\n  private static String ALIAS_ITEM = \"item\";\n\n  private static String LIST_ID = ALIAS_LIST + \".\" + TodoList.ID;\n  private static String LIST_NAME = ALIAS_LIST + \".\" + TodoList.NAME;\n  private static String ITEM_COUNT = \"item_count\";\n  private static String ITEM_ID = ALIAS_ITEM + \".\" + TodoItem.ID;\n  private static String ITEM_LIST_ID = ALIAS_ITEM + \".\" + TodoItem.LIST_ID;\n\n  public static Collection<String> TABLES = Arrays.asList(TodoList.TABLE, TodoItem.TABLE);\n  public static String QUERY = \"\"\n      + \"SELECT \" + LIST_ID + \", \" + LIST_NAME + \", COUNT(\" + ITEM_ID + \") as \" + ITEM_COUNT\n      + \" FROM \" + TodoList.TABLE + \" AS \" + ALIAS_LIST\n      + \" LEFT OUTER JOIN \" + TodoItem.TABLE + \" AS \" + ALIAS_ITEM + \" ON \" + LIST_ID + \" = \" + ITEM_LIST_ID\n      + \" GROUP BY \" + LIST_ID;\n\n  abstract long id();\n  abstract String name();\n  abstract int itemCount();\n\n  static Function<Cursor, ListsItem> MAPPER = new Function<Cursor, ListsItem>() {\n    @Override public ListsItem apply(Cursor cursor) {\n      long id = Db.getLong(cursor, TodoList.ID);\n      String name = Db.getString(cursor, TodoList.NAME);\n      int itemCount = Db.getInt(cursor, ITEM_COUNT);\n      return new AutoValue_ListsItem(id, name, itemCount);\n    }\n  };\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/MainActivity.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.os.Bundle;\nimport android.support.v4.app.FragmentActivity;\nimport com.example.sqlbrite.todo.R;\n\npublic final class MainActivity extends FragmentActivity\n    implements ListsFragment.Listener, ItemsFragment.Listener {\n\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    if (savedInstanceState == null) {\n      getSupportFragmentManager().beginTransaction()\n          .add(android.R.id.content, ListsFragment.newInstance())\n          .commit();\n    }\n  }\n\n  @Override public void onListClicked(long id) {\n    getSupportFragmentManager().beginTransaction()\n        .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left,\n            R.anim.slide_out_right)\n        .replace(android.R.id.content, ItemsFragment.newInstance(id))\n        .addToBackStack(null)\n        .commit();\n  }\n\n  @Override public void onNewListClicked() {\n    NewListFragment.newInstance().show(getSupportFragmentManager(), \"new-list\");\n  }\n\n  @Override public void onNewItemClicked(long listId) {\n    NewItemFragment.newInstance(listId).show(getSupportFragmentManager(), \"new-item\");\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/NewItemFragment.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.DialogFragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.EditText;\nimport com.example.sqlbrite.todo.R;\nimport com.example.sqlbrite.todo.TodoApp;\nimport com.example.sqlbrite.todo.db.TodoItem;\nimport com.jakewharton.rxbinding2.widget.RxTextView;\nimport com.squareup.sqlbrite3.BriteDatabase;\nimport io.reactivex.Observable;\nimport io.reactivex.functions.BiFunction;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.schedulers.Schedulers;\nimport io.reactivex.subjects.PublishSubject;\nimport javax.inject.Inject;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_NONE;\nimport static butterknife.ButterKnife.findById;\n\npublic final class NewItemFragment extends DialogFragment {\n  private static final String KEY_LIST_ID = \"list_id\";\n\n  public static NewItemFragment newInstance(long listId) {\n    Bundle arguments = new Bundle();\n    arguments.putLong(KEY_LIST_ID, listId);\n\n    NewItemFragment fragment = new NewItemFragment();\n    fragment.setArguments(arguments);\n    return fragment;\n  }\n\n  private final PublishSubject<String> createClicked = PublishSubject.create();\n\n  @Inject BriteDatabase db;\n\n  private long getListId() {\n    return getArguments().getLong(KEY_LIST_ID);\n  }\n\n  @Override public void onAttach(Activity activity) {\n    super.onAttach(activity);\n    TodoApp.getComponent(activity).inject(this);\n  }\n\n  @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) {\n    final Context context = getActivity();\n    View view = LayoutInflater.from(context).inflate(R.layout.new_item, null);\n\n    EditText name = findById(view, android.R.id.input);\n    Observable.combineLatest(createClicked, RxTextView.textChanges(name),\n        new BiFunction<String, CharSequence, String>() {\n          @Override public String apply(String ignored, CharSequence text) {\n            return text.toString();\n          }\n        }) //\n        .observeOn(Schedulers.io())\n        .subscribe(new Consumer<String>() {\n          @Override public void accept(String description) {\n            db.insert(TodoItem.TABLE, CONFLICT_NONE,\n                new TodoItem.Builder().listId(getListId()).description(description).build());\n          }\n        });\n\n    return new AlertDialog.Builder(context) //\n        .setTitle(R.string.new_item)\n        .setView(view)\n        .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {\n          @Override public void onClick(DialogInterface dialog, int which) {\n            createClicked.onNext(\"clicked\");\n          }\n        })\n        .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {\n          @Override public void onClick(@NonNull DialogInterface dialog, int which) {\n          }\n        })\n        .create();\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/example/sqlbrite/todo/ui/NewListFragment.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.sqlbrite.todo.ui;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.content.DialogInterface;\nimport android.os.Bundle;\nimport android.support.annotation.NonNull;\nimport android.support.v4.app.DialogFragment;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.EditText;\nimport com.example.sqlbrite.todo.R;\nimport com.example.sqlbrite.todo.TodoApp;\nimport com.example.sqlbrite.todo.db.TodoList;\nimport com.jakewharton.rxbinding2.widget.RxTextView;\nimport com.squareup.sqlbrite3.BriteDatabase;\nimport io.reactivex.Observable;\nimport io.reactivex.functions.BiFunction;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.schedulers.Schedulers;\nimport io.reactivex.subjects.PublishSubject;\nimport javax.inject.Inject;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_NONE;\nimport static butterknife.ButterKnife.findById;\n\npublic final class NewListFragment extends DialogFragment {\n  public static NewListFragment newInstance() {\n    return new NewListFragment();\n  }\n\n  private final PublishSubject<String> createClicked = PublishSubject.create();\n\n  @Inject BriteDatabase db;\n\n  @Override public void onAttach(Activity activity) {\n    super.onAttach(activity);\n    TodoApp.getComponent(activity).inject(this);\n  }\n\n  @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) {\n    final Context context = getActivity();\n    View view = LayoutInflater.from(context).inflate(R.layout.new_list, null);\n\n    EditText name = findById(view, android.R.id.input);\n    Observable.combineLatest(createClicked, RxTextView.textChanges(name),\n        new BiFunction<String, CharSequence, String>() {\n          @Override public String apply(String ignored, CharSequence text) {\n            return text.toString();\n          }\n        }) //\n        .observeOn(Schedulers.io())\n        .subscribe(new Consumer<String>() {\n          @Override public void accept(String name) {\n            db.insert(TodoList.TABLE, CONFLICT_NONE, new TodoList.Builder().name(name).build());\n          }\n        });\n\n    return new AlertDialog.Builder(context) //\n        .setTitle(R.string.new_list)\n        .setView(view)\n        .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {\n          @Override public void onClick(DialogInterface dialog, int which) {\n            createClicked.onNext(\"clicked\");\n          }\n        })\n        .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {\n          @Override public void onClick(@NonNull DialogInterface dialog, int which) {\n          }\n        })\n        .create();\n  }\n}\n"
  },
  {
    "path": "sample/src/main/res/anim/slide_in_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_left.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"-25%p\" android:toXDelta=\"0\"\n            android:duration=\"@android:integer/config_shortAnimTime\"/>\n\t<alpha android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n            android:duration=\"@android:integer/config_shortAnimTime\" />\n</set>\n"
  },
  {
    "path": "sample/src/main/res/anim/slide_in_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_in_right.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"25%p\" android:toXDelta=\"0\"\n            android:duration=\"@android:integer/config_shortAnimTime\"/>\n\t<alpha android:fromAlpha=\"0.0\" android:toAlpha=\"1.0\"\n            android:duration=\"@android:integer/config_shortAnimTime\" />\n</set>\n"
  },
  {
    "path": "sample/src/main/res/anim/slide_out_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_left.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"0\" android:toXDelta=\"-25%p\"\n            android:duration=\"@android:integer/config_shortAnimTime\"/>\n\t<alpha android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n            android:duration=\"@android:integer/config_shortAnimTime\" />\n</set>\n"
  },
  {
    "path": "sample/src/main/res/anim/slide_out_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n/* //device/apps/common/res/anim/slide_out_right.xml\n**\n** Copyright 2007, The Android Open Source Project\n**\n** Licensed under the Apache License, Version 2.0 (the \"License\");\n** you may not use this file except in compliance with the License.\n** You may obtain a copy of the License at\n**\n**     http://www.apache.org/licenses/LICENSE-2.0\n**\n** Unless required by applicable law or agreed to in writing, software\n** distributed under the License is distributed on an \"AS IS\" BASIS,\n** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n** See the License for the specific language governing permissions and\n** limitations under the License.\n*/\n-->\n\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\t<translate android:fromXDelta=\"0\" android:toXDelta=\"25%p\"\n            android:duration=\"@android:integer/config_shortAnimTime\"/>\n\t<alpha android:fromAlpha=\"1.0\" android:toAlpha=\"0.0\"\n            android:duration=\"@android:integer/config_shortAnimTime\" />\n</set>\n"
  },
  {
    "path": "sample/src/main/res/layout/items.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n  <!-- TODO get nice empty view / icon -->\n  <ImageView\n      android:id=\"@android:id/empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      />\n  <ListView\n      android:id=\"@android:id/list\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n</FrameLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/lists.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    >\n  <!-- TODO get nice empty view / icon -->\n  <ImageView\n      android:id=\"@android:id/empty\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center\"\n      />\n  <ListView\n      android:id=\"@android:id/list\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"match_parent\"\n      />\n</FrameLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/new_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"12dp\"\n    >\n  <EditText\n      android:id=\"@android:id/input\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:inputType=\"textCapSentences|textAutoCorrect\"\n      android:hint=\"@string/new_item_hint\"\n      />\n</FrameLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/new_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"12dp\"\n    >\n  <EditText\n      android:id=\"@android:id/input\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:inputType=\"textCapWords|textAutoCorrect\"\n      android:hint=\"@string/new_list_hint\"\n      />\n</FrameLayout>\n"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n  <string name=\"app_name\">SqlBrite To-Do</string>\n\n  <string name=\"create\">Create</string>\n  <string name=\"cancel\">Cancel</string>\n  <string name=\"new_list\">New List</string>\n  <string name=\"new_list_hint\">Name</string>\n  <string name=\"new_item\">New Item</string>\n  <string name=\"new_item_hint\">Description</string>\n</resources>\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':sqlbrite'\ninclude ':sqlbrite-kotlin'\ninclude ':sqlbrite-lint'\ninclude ':sample'\n\nrootProject.name = 'sqlbrite-root'\n"
  },
  {
    "path": "sqlbrite/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\ndependencies {\n  api rootProject.ext.rxJava\n  api rootProject.ext.supportSqlite\n  implementation rootProject.ext.supportAnnotations\n\n  androidTestImplementation rootProject.ext.supportTestRunner\n  androidTestImplementation rootProject.ext.truth\n  androidTestImplementation rootProject.ext.supportSqliteFramework\n\n  lintChecks project(':sqlbrite-lint')\n}\n\nandroid {\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n\n    testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'\n  }\n\n  compileOptions {\n    sourceCompatibility JavaVersion.VERSION_1_7\n    targetCompatibility JavaVersion.VERSION_1_7\n  }\n\n  lintOptions {\n    textOutput 'stdout'\n    textReport true\n  }\n\n  // TODO replace with https://issuetracker.google.com/issues/72050365 once released.\n  libraryVariants.all {\n    it.generateBuildConfig.enabled = false\n  }\n}\n\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')\n"
  },
  {
    "path": "sqlbrite/gradle.properties",
    "content": "POM_ARTIFACT_ID=sqlbrite\nPOM_NAME=SqlBrite\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/BlockingRecordingObserver.java",
    "content": "/*\n * Copyright (C) 2016 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static java.util.concurrent.TimeUnit.SECONDS;\n\nfinal class BlockingRecordingObserver extends RecordingObserver {\n  protected Object takeEvent() {\n    try {\n      Object item = events.pollFirst(1, SECONDS);\n      if (item == null) {\n        throw new AssertionError(\"No items.\");\n      }\n      return item;\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Override public void assertNoMoreEvents() {\n    try {\n      assertThat(events.pollFirst(1, SECONDS)).isNull();\n    } catch (InterruptedException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/BriteContentResolverTest.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.MatrixCursor;\nimport android.net.Uri;\nimport android.test.ProviderTestCase2;\nimport android.test.mock.MockContentProvider;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableSource;\nimport io.reactivex.ObservableTransformer;\nimport io.reactivex.subjects.PublishSubject;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static com.google.common.truth.Truth.assertThat;\n\npublic final class BriteContentResolverTest\n    extends ProviderTestCase2<BriteContentResolverTest.TestContentProvider> {\n  private static final Uri AUTHORITY = Uri.parse(\"content://test_authority\");\n  private static final Uri TABLE = AUTHORITY.buildUpon().appendPath(\"test_table\").build();\n  private static final String KEY = \"test_key\";\n  private static final String VALUE = \"test_value\";\n\n  private final List<String> logs = new ArrayList<>();\n  private final RecordingObserver o = new BlockingRecordingObserver();\n  private final TestScheduler scheduler = new TestScheduler();\n  private final PublishSubject<Object> killSwitch = PublishSubject.create();\n\n  private ContentResolver contentResolver;\n  private BriteContentResolver db;\n\n  public BriteContentResolverTest() {\n    super(TestContentProvider.class, AUTHORITY.getAuthority());\n  }\n\n  @Override protected void setUp() throws Exception {\n    super.setUp();\n    contentResolver = getMockContentResolver();\n\n    SqlBrite.Logger logger = new SqlBrite.Logger() {\n      @Override public void log(String message) {\n        logs.add(message);\n      }\n    };\n    ObservableTransformer<Query, Query> queryTransformer =\n        new ObservableTransformer<Query, Query>() {\n          @Override public ObservableSource<Query> apply(Observable<Query> upstream) {\n            return upstream.takeUntil(killSwitch);\n          }\n        };\n    db = new BriteContentResolver(contentResolver, logger, scheduler, queryTransformer);\n\n    getProvider().init(getContext().getContentResolver());\n  }\n\n  @Override public void tearDown() {\n    o.assertNoMoreEvents();\n    o.dispose();\n  }\n\n  public void testLoggerEnabled() {\n    db.setLoggingEnabled(true);\n\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().isExhausted();\n\n    contentResolver.insert(TABLE, values(\"key1\", \"value1\"));\n    o.assertCursor().hasRow(\"key1\", \"value1\").isExhausted();\n    assertThat(logs).isNotEmpty();\n  }\n\n  public void testLoggerDisabled() {\n    db.setLoggingEnabled(false);\n\n    contentResolver.insert(TABLE, values(\"key1\", \"value1\"));\n    assertThat(logs).isEmpty();\n  }\n\n  public void testCreateQueryObservesInsert() {\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().isExhausted();\n\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    o.assertCursor().hasRow(\"key1\", \"val1\").isExhausted();\n  }\n\n  public void testCreateQueryObservesUpdate() {\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().hasRow(\"key1\", \"val1\").isExhausted();\n\n    contentResolver.update(TABLE, values(\"key1\", \"val2\"), null, null);\n    o.assertCursor().hasRow(\"key1\", \"val2\").isExhausted();\n  }\n\n  public void testCreateQueryObservesDelete() {\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().hasRow(\"key1\", \"val1\").isExhausted();\n\n    contentResolver.delete(TABLE, null, null);\n    o.assertCursor().isExhausted();\n  }\n\n  public void testUnsubscribeDoesNotTrigger() {\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().isExhausted();\n    o.dispose();\n\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    o.assertNoMoreEvents();\n    assertThat(logs).isEmpty();\n  }\n\n  public void testQueryNotNotifiedWhenQueryTransformerDisposed() {\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertCursor().isExhausted();\n\n    killSwitch.onNext(\"kill\");\n    o.assertIsCompleted();\n\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    o.assertNoMoreEvents();\n  }\n\n  public void testInitialValueAndTriggerUsesScheduler() {\n    scheduler.runTasksImmediately(false);\n\n    db.createQuery(TABLE, null, null, null, null, false).subscribe(o);\n    o.assertNoMoreEvents();\n    scheduler.triggerActions();\n    o.assertCursor().isExhausted();\n\n    contentResolver.insert(TABLE, values(\"key1\", \"val1\"));\n    o.assertNoMoreEvents();\n    scheduler.triggerActions();\n    o.assertCursor().hasRow(\"key1\", \"val1\").isExhausted();\n  }\n\n  private ContentValues values(String key, String value) {\n    ContentValues result = new ContentValues();\n    result.put(KEY, key);\n    result.put(VALUE, value);\n    return result;\n  }\n\n  public static final class TestContentProvider extends MockContentProvider {\n    private final Map<String, String> storage = new LinkedHashMap<>();\n\n    private ContentResolver contentResolver;\n\n    void init(ContentResolver contentResolver) {\n      this.contentResolver = contentResolver;\n    }\n\n    @Override public Uri insert(Uri uri, ContentValues values) {\n      storage.put(values.getAsString(KEY), values.getAsString(VALUE));\n      contentResolver.notifyChange(uri, null);\n      return Uri.parse(AUTHORITY + \"/\" + values.getAsString(KEY));\n    }\n\n    @Override public int update(Uri uri, ContentValues values, String selection,\n        String[] selectionArgs) {\n      for (String key : storage.keySet()) {\n        storage.put(key, values.getAsString(VALUE));\n      }\n      contentResolver.notifyChange(uri, null);\n      return storage.size();\n    }\n\n    @Override public int delete(Uri uri, String selection, String[] selectionArgs) {\n      int result = storage.size();\n      storage.clear();\n      contentResolver.notifyChange(uri, null);\n      return result;\n    }\n\n    @Override public Cursor query(Uri uri, String[] projection, String selection,\n        String[] selectionArgs, String sortOrder) {\n      MatrixCursor result = new MatrixCursor(new String[] { KEY, VALUE });\n      for (Map.Entry<String, String> entry : storage.entrySet()) {\n        result.addRow(new Object[] { entry.getKey(), entry.getValue() });\n      }\n      return result;\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/BriteDatabaseTest.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.annotation.TargetApi;\nimport android.arch.persistence.db.SimpleSQLiteQuery;\nimport android.arch.persistence.db.SupportSQLiteDatabase;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Factory;\nimport android.arch.persistence.db.SupportSQLiteStatement;\nimport android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.SQLException;\nimport android.database.sqlite.SQLiteException;\nimport android.os.Build;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.filters.SdkSuppress;\nimport android.support.test.runner.AndroidJUnit4;\nimport com.squareup.sqlbrite3.BriteDatabase.Transaction;\nimport com.squareup.sqlbrite3.RecordingObserver.CursorAssert;\nimport com.squareup.sqlbrite3.TestDb.Employee;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableSource;\nimport io.reactivex.ObservableTransformer;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.subjects.PublishSubject;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.CountDownLatch;\nimport org.junit.After;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.rules.TemporaryFolder;\nimport org.junit.runner.RunWith;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_NONE;\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.squareup.sqlbrite3.SqlBrite.Query;\nimport static com.squareup.sqlbrite3.TestDb.BOTH_TABLES;\nimport static com.squareup.sqlbrite3.TestDb.EmployeeTable.NAME;\nimport static com.squareup.sqlbrite3.TestDb.EmployeeTable.USERNAME;\nimport static com.squareup.sqlbrite3.TestDb.SELECT_EMPLOYEES;\nimport static com.squareup.sqlbrite3.TestDb.SELECT_MANAGER_LIST;\nimport static com.squareup.sqlbrite3.TestDb.TABLE_EMPLOYEE;\nimport static com.squareup.sqlbrite3.TestDb.TABLE_MANAGER;\nimport static com.squareup.sqlbrite3.TestDb.employee;\nimport static com.squareup.sqlbrite3.TestDb.manager;\nimport static java.util.concurrent.TimeUnit.MILLISECONDS;\nimport static java.util.concurrent.TimeUnit.SECONDS;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class) //\npublic final class BriteDatabaseTest {\n  private final TestDb testDb = new TestDb();\n  private final List<String> logs = new ArrayList<>();\n  private final RecordingObserver o = new RecordingObserver();\n  private final TestScheduler scheduler = new TestScheduler();\n  private final PublishSubject<Object> killSwitch = PublishSubject.create();\n\n  @Rule public final TemporaryFolder dbFolder = new TemporaryFolder();\n\n  private SupportSQLiteDatabase real;\n  private BriteDatabase db;\n\n  @Before public void setUp() throws IOException {\n    Configuration configuration = Configuration.builder(InstrumentationRegistry.getContext())\n        .callback(testDb)\n        .name(dbFolder.newFile().getPath())\n        .build();\n\n    Factory factory = new FrameworkSQLiteOpenHelperFactory();\n    SupportSQLiteOpenHelper helper = factory.create(configuration);\n    real = helper.getWritableDatabase();\n\n    SqlBrite.Logger logger = new SqlBrite.Logger() {\n      @Override public void log(String message) {\n        logs.add(message);\n      }\n    };\n    ObservableTransformer<Query, Query> queryTransformer =\n        new ObservableTransformer<Query, Query>() {\n          @Override public ObservableSource<Query> apply(Observable<Query> upstream) {\n            return upstream.takeUntil(killSwitch);\n          }\n        };\n    db = new BriteDatabase(helper, logger, scheduler, queryTransformer);\n  }\n\n  @After public void tearDown() {\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void loggerEnabled() {\n    db.setLoggingEnabled(true);\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    assertThat(logs).isNotEmpty();\n  }\n\n  @Test public void loggerDisabled() {\n    db.setLoggingEnabled(false);\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    assertThat(logs).isEmpty();\n  }\n\n  @Test public void loggerIndentsSqlForCreateQuery() {\n    db.setLoggingEnabled(true);\n    QueryObservable query = db.createQuery(TABLE_EMPLOYEE, \"SELECT\\n1\");\n    query.subscribe(new Consumer<Query>() {\n      @Override public void accept(Query query) throws Exception {\n        query.run().close();\n      }\n    });\n    assertThat(logs).containsExactly(\"\"\n        + \"QUERY\\n\"\n        + \"  tables: [employee]\\n\"\n        + \"  sql: SELECT\\n\"\n        + \"       1\");\n  }\n\n  @Test public void loggerIndentsSqlForQuery() {\n    db.setLoggingEnabled(true);\n    db.query(\"SELECT\\n1\").close();\n    assertThat(logs).containsExactly(\"\"\n        + \"QUERY\\n\"\n        + \"  sql: SELECT\\n\"\n        + \"       1\\n\"\n        + \"  args: []\");\n  }\n\n  @Test public void loggerIndentsSqlForExecute() {\n    db.setLoggingEnabled(true);\n    db.execute(\"PRAGMA\\ncompile_options\");\n    assertThat(logs).containsExactly(\"\"\n        + \"EXECUTE\\n\"\n        + \"  sql: PRAGMA\\n\"\n        + \"       compile_options\");\n  }\n\n  @Test public void loggerIndentsSqlForExecuteWithArgs() {\n    db.setLoggingEnabled(true);\n    db.execute(\"PRAGMA\\ncompile_options\", new Object[0]);\n    assertThat(logs).containsExactly(\"\"\n        + \"EXECUTE\\n\"\n        + \"  sql: PRAGMA\\n\"\n        + \"       compile_options\\n\"\n        + \"  args: []\");\n  }\n\n  @Test public void closePropagates() {\n    db.close();\n    assertThat(real.isOpen()).isFalse();\n  }\n\n  @Test public void query() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryWithQueryObject() {\n    db.createQuery(TABLE_EMPLOYEE, new SimpleSQLiteQuery(SELECT_EMPLOYEES)).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryMapToList() {\n    List<Employee> employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .mapToList(Employee.MAPPER)\n        .blockingFirst();\n    assertThat(employees).containsExactly( //\n        new Employee(\"alice\", \"Alice Allison\"), //\n        new Employee(\"bob\", \"Bob Bobberson\"), //\n        new Employee(\"eve\", \"Eve Evenson\"));\n  }\n\n  @Test public void queryMapToOne() {\n    Employee employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .mapToOne(Employee.MAPPER)\n        .blockingFirst();\n    assertThat(employees).isEqualTo(new Employee(\"alice\", \"Alice Allison\"));\n  }\n\n  @Test public void queryMapToOneOrDefault() {\n    Employee employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .mapToOneOrDefault(Employee.MAPPER, new Employee(\"wrong\", \"Wrong Person\"))\n        .blockingFirst();\n    assertThat(employees).isEqualTo(new Employee(\"alice\", \"Alice Allison\"));\n  }\n\n  @Test public void badQueryCallsError() {\n    // safeSubscribe is needed because the error occurs in onNext and will otherwise bubble up\n    // to the thread exception handler.\n    db.createQuery(TABLE_EMPLOYEE, \"SELECT * FROM missing\").safeSubscribe(o);\n    o.assertErrorContains(\"no such table: missing\");\n  }\n\n  @Test public void queryWithArgs() {\n    db.createQuery(\n        TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" WHERE \" + USERNAME + \" = ?\", \"bob\")\n        .subscribe(o);\n    o.assertCursor()\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .isExhausted();\n  }\n\n  @Test public void queryObservesInsert() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .isExhausted();\n  }\n\n  @Test public void queryInitialValueAndTriggerUsesScheduler() {\n    scheduler.runTasksImmediately(false);\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertNoMoreEvents();\n    scheduler.triggerActions();\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertNoMoreEvents();\n    scheduler.triggerActions();\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .isExhausted();\n  }\n\n  @Test public void queryNotNotifiedWhenInsertFails() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_IGNORE, employee(\"bob\", \"Bob Bobberson\"));\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void queryNotNotifiedWhenQueryTransformerUnsubscribes() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    killSwitch.onNext(\"kill\");\n    o.assertIsCompleted();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void queryObservesUpdate() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    ContentValues values = new ContentValues();\n    values.put(NAME, \"Robert Bobberson\");\n    db.update(TABLE_EMPLOYEE, CONFLICT_NONE, values, USERNAME + \" = 'bob'\");\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Robert Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryNotNotifiedWhenUpdateAffectsZeroRows() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    ContentValues values = new ContentValues();\n    values.put(NAME, \"John Johnson\");\n    db.update(TABLE_EMPLOYEE, CONFLICT_NONE, values, USERNAME + \" = 'john'\");\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void queryObservesDelete() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.delete(TABLE_EMPLOYEE, USERNAME + \" = 'bob'\");\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryNotNotifiedWhenDeleteAffectsZeroRows() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.delete(TABLE_EMPLOYEE, USERNAME + \" = 'john'\");\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void queryMultipleTables() {\n    db.createQuery(BOTH_TABLES, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n  }\n\n  @Test public void queryMultipleTablesWithQueryObject() {\n    db.createQuery(BOTH_TABLES, new SimpleSQLiteQuery(SELECT_MANAGER_LIST)).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n  }\n\n  @Test public void queryMultipleTablesObservesChanges() {\n    db.createQuery(BOTH_TABLES, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    // A new employee triggers, despite the fact that it's not in our result set.\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    // A new manager also triggers and it is in our result set.\n    db.insert(TABLE_MANAGER, CONFLICT_NONE, manager(testDb.bobId, testDb.eveId));\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .hasRow(\"Bob Bobberson\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryMultipleTablesObservesChangesOnlyOnce() {\n    // Employee table is in this list twice. We should still only be notified once for a change.\n    List<String> tables = Arrays.asList(TABLE_EMPLOYEE, TABLE_MANAGER, TABLE_EMPLOYEE);\n    db.createQuery(tables, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    ContentValues values = new ContentValues();\n    values.put(NAME, \"Even Evenson\");\n    db.update(TABLE_EMPLOYEE, CONFLICT_NONE, values, USERNAME + \" = 'eve'\");\n    o.assertCursor()\n        .hasRow(\"Even Evenson\", \"Alice Allison\")\n        .isExhausted();\n  }\n\n  @Test public void queryNotNotifiedAfterDispose() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n    o.dispose();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void queryOnlyNotifiedAfterSubscribe() {\n    Observable<Query> query = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES);\n    o.assertNoMoreEvents();\n\n    db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n    o.assertNoMoreEvents();\n\n    query.subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .isExhausted();\n  }\n\n  @Test public void executeSqlNoTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    db.execute(\"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeSqlWithArgsNoTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    db.execute(\"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = ?\", \"Zach\");\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeSqlAndTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeAndTrigger(TABLE_EMPLOYEE,\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n    o.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @Test public void executeSqlAndTriggerMultipleTables() {\n    db.createQuery(TABLE_MANAGER, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n    final RecordingObserver employeeObserver = new RecordingObserver();\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(employeeObserver);\n    employeeObserver.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    final Set<String> tablesToTrigger = Collections.unmodifiableSet(new HashSet<>(BOTH_TABLES));\n    db.executeAndTrigger(tablesToTrigger,\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n\n    o.assertCursor()\n        .hasRow(\"Zach\", \"Zach\")\n        .isExhausted();\n    employeeObserver.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @Test public void executeSqlAndTriggerWithNoTables() {\n    db.createQuery(TABLE_MANAGER, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    db.executeAndTrigger(Collections.<String>emptySet(),\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeSqlThrowsAndDoesNotTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeAndTrigger(TABLE_EMPLOYEE,\n          \"UPDATE not_a_table SET \" + NAME + \" = 'Zach'\");\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeSqlWithArgsAndTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeAndTrigger(TABLE_EMPLOYEE,\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = ?\", \"Zach\");\n    o.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @Test public void executeSqlWithArgsThrowsAndDoesNotTrigger() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeAndTrigger(TABLE_EMPLOYEE,\n          \"UPDATE not_a_table SET \" + NAME + \" = ?\", \"Zach\");\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeSqlWithArgsAndTriggerWithMultipleTables() {\n    db.createQuery(TABLE_MANAGER, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n    final RecordingObserver employeeObserver = new RecordingObserver();\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(employeeObserver);\n    employeeObserver.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    final Set<String> tablesToTrigger = Collections.unmodifiableSet(new HashSet<>(BOTH_TABLES));\n    db.executeAndTrigger(tablesToTrigger,\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = ?\", \"Zach\");\n\n    o.assertCursor()\n        .hasRow(\"Zach\", \"Zach\")\n        .isExhausted();\n    employeeObserver.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @Test public void executeSqlWithArgsAndTriggerWithNoTables() {\n    db.createQuery(BOTH_TABLES, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    db.executeAndTrigger(Collections.<String>emptySet(),\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = ?\", \"Zach\");\n\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeInsertAndTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") \"\n        + \"VALUES ('Chad Chadson', 'chad')\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeInsert(TABLE_EMPLOYEE, statement);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"chad\", \"Chad Chadson\")\n        .isExhausted();\n  }\n\n  @Test public void executeInsertAndDontTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT OR IGNORE INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") \"\n        + \"VALUES ('Alice Allison', 'alice')\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeInsert(TABLE_EMPLOYEE, statement);\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeInsertAndTriggerMultipleTables() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") \"\n        + \"VALUES ('Chad Chadson', 'chad')\");\n\n    final RecordingObserver managerObserver = new RecordingObserver();\n    db.createQuery(TABLE_MANAGER, SELECT_MANAGER_LIST).subscribe(managerObserver);\n    managerObserver.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    final Set<String> employeeAndManagerTables = Collections.unmodifiableSet(new HashSet<>(\n        BOTH_TABLES));\n    db.executeInsert(employeeAndManagerTables, statement);\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"chad\", \"Chad Chadson\")\n        .isExhausted();\n    managerObserver.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n  }\n\n  @Test public void executeInsertAndTriggerNoTables() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") \"\n        + \"VALUES ('Chad Chadson', 'chad')\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeInsert(Collections.<String>emptySet(), statement);\n\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeInsertThrowsAndDoesNotTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") \"\n        + \"VALUES ('Alice Allison', 'alice')\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeInsert(TABLE_EMPLOYEE, statement);\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void executeInsertWithArgsAndTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") VALUES (?, ?)\");\n    statement.bindString(1, \"Chad Chadson\");\n    statement.bindString(2, \"chad\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeInsert(TABLE_EMPLOYEE, statement);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"chad\", \"Chad Chadson\")\n        .isExhausted();\n  }\n\n  @Test public void executeInsertWithArgsThrowsAndDoesNotTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"INSERT INTO \"\n        + TABLE_EMPLOYEE + \" (\" + NAME + \", \" + USERNAME + \") VALUES (?, ?)\");\n    statement.bindString(1, \"Alice Aliison\");\n    statement.bindString(2, \"alice\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeInsert(TABLE_EMPLOYEE, statement);\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteAndTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeUpdateDelete(TABLE_EMPLOYEE, statement);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteAndDontTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\"\"\n        + \"UPDATE \" + TABLE_EMPLOYEE\n        + \" SET \" + NAME + \" = 'Zach'\"\n        + \" WHERE \" + NAME + \" = 'Rob'\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeUpdateDelete(TABLE_EMPLOYEE, statement);\n    o.assertNoMoreEvents();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteAndTriggerWithMultipleTables() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n\n\n    final RecordingObserver managerObserver = new RecordingObserver();\n    db.createQuery(TABLE_MANAGER, SELECT_MANAGER_LIST).subscribe(managerObserver);\n    managerObserver.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    final Set<String> employeeAndManagerTables = Collections.unmodifiableSet(new HashSet<>(BOTH_TABLES));\n    db.executeUpdateDelete(employeeAndManagerTables, statement);\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n    managerObserver.assertCursor()\n        .hasRow(\"Zach\", \"Zach\")\n        .isExhausted();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteAndTriggerWithNoTables() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = 'Zach'\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeUpdateDelete(Collections.<String>emptySet(), statement);\n\n    o.assertNoMoreEvents();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteThrowsAndDoesNotTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + USERNAME + \" = 'alice'\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeUpdateDelete(TABLE_EMPLOYEE, statement);\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteWithArgsAndTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + NAME + \" = ?\");\n    statement.bindString(1, \"Zach\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    db.executeUpdateDelete(TABLE_EMPLOYEE, statement);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Zach\")\n        .hasRow(\"bob\", \"Zach\")\n        .hasRow(\"eve\", \"Zach\")\n        .isExhausted();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void executeUpdateDeleteWithArgsThrowsAndDoesNotTrigger() {\n    SupportSQLiteStatement statement = real.compileStatement(\n        \"UPDATE \" + TABLE_EMPLOYEE + \" SET \" + USERNAME + \" = ?\");\n    statement.bindString(1, \"alice\");\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .skip(1) // Skip initial\n        .subscribe(o);\n\n    try {\n      db.executeUpdateDelete(TABLE_EMPLOYEE, statement);\n      fail();\n    } catch (SQLException ignored) {\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void transactionOnlyNotifiesOnce() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transaction = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n      o.assertNoMoreEvents();\n\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .hasRow(\"nick\", \"Nick Nickers\")\n        .isExhausted();\n  }\n\n  @Test public void transactionCreatedFromTransactionNotificationWorks() {\n    // Tests the case where a transaction is created in the subscriber to a query which gets\n    // notified as the result of another transaction being committed. With improper ordering, this\n    // can result in creating a new transaction before the old is committed on the underlying DB.\n\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .subscribe(new Consumer<Query>() {\n          @Override public void accept(Query query) {\n            db.newTransaction().end();\n          }\n        });\n\n    Transaction transaction = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n  }\n\n  @Test public void transactionIsCloseable() throws IOException {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transaction = db.newTransaction();\n    //noinspection UnnecessaryLocalVariable\n    Closeable closeableTransaction = transaction; // Verify type is implemented.\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n      transaction.markSuccessful();\n    } finally {\n      closeableTransaction.close();\n    }\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .hasRow(\"nick\", \"Nick Nickers\")\n        .isExhausted();\n  }\n\n  @Test public void transactionDoesNotThrow() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transaction = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n      transaction.markSuccessful();\n    } finally {\n      transaction.close(); // Transactions should not throw on close().\n    }\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .hasRow(\"nick\", \"Nick Nickers\")\n        .isExhausted();\n  }\n\n  @Test public void queryCreatedDuringTransactionThrows() {\n    //noinspection CheckResult\n    db.newTransaction();\n    try {\n      //noinspection CheckResult\n      db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES);\n      fail();\n    } catch (IllegalStateException e) {\n      assertThat(e.getMessage()).startsWith(\"Cannot create observable query in transaction.\");\n    }\n  }\n\n  @Test public void querySubscribedToDuringTransactionThrows() {\n    Observable<Query> query = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES);\n\n    db.newTransaction();\n    query.subscribe(o);\n    o.assertErrorContains(\"Cannot subscribe to observable query in a transaction.\");\n  }\n\n  @Test public void callingEndMultipleTimesThrows() {\n    Transaction transaction = db.newTransaction();\n    transaction.end();\n    try {\n      transaction.end();\n      fail();\n    } catch (IllegalStateException e) {\n      assertThat(e).hasMessage(\"Not in transaction.\");\n    }\n  }\n\n  @Test public void querySubscribedToDuringTransactionOnDifferentThread()\n      throws InterruptedException {\n    Transaction transaction = db.newTransaction();\n\n    final CountDownLatch latch = new CountDownLatch(1);\n    new Thread() {\n      @Override public void run() {\n        db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n        latch.countDown();\n      }\n    }.start();\n\n    Thread.sleep(500); // Wait for the thread to block on initial query.\n    o.assertNoMoreEvents();\n\n    transaction.end(); // Allow other queries to continue.\n    latch.await(500, MILLISECONDS); // Wait for thread to observe initial query.\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n  }\n\n  @Test public void queryCreatedBeforeTransactionButSubscribedAfter() {\n    Observable<Query> query = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES);\n\n    Transaction transaction = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n\n    query.subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .hasRow(\"nick\", \"Nick Nickers\")\n        .isExhausted();\n  }\n\n  @Test public void synchronousQueryDuringTransaction() {\n    Transaction transaction = db.newTransaction();\n    try {\n      transaction.markSuccessful();\n      assertCursor(db.query(SELECT_EMPLOYEES))\n          .hasRow(\"alice\", \"Alice Allison\")\n          .hasRow(\"bob\", \"Bob Bobberson\")\n          .hasRow(\"eve\", \"Eve Evenson\")\n          .isExhausted();\n    } finally {\n      transaction.end();\n    }\n  }\n\n  @Test public void synchronousQueryDuringTransactionSeesChanges() {\n    Transaction transaction = db.newTransaction();\n    try {\n      assertCursor(db.query(SELECT_EMPLOYEES))\n          .hasRow(\"alice\", \"Alice Allison\")\n          .hasRow(\"bob\", \"Bob Bobberson\")\n          .hasRow(\"eve\", \"Eve Evenson\")\n          .isExhausted();\n\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n\n      assertCursor(db.query(SELECT_EMPLOYEES))\n          .hasRow(\"alice\", \"Alice Allison\")\n          .hasRow(\"bob\", \"Bob Bobberson\")\n          .hasRow(\"eve\", \"Eve Evenson\")\n          .hasRow(\"john\", \"John Johnson\")\n          .hasRow(\"nick\", \"Nick Nickers\")\n          .isExhausted();\n\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n  }\n\n  @Test public void synchronousQueryWithSupportSQLiteQueryDuringTransaction() {\n    Transaction transaction = db.newTransaction();\n    try {\n      transaction.markSuccessful();\n      assertCursor(db.query(new SimpleSQLiteQuery(SELECT_EMPLOYEES)))\n              .hasRow(\"alice\", \"Alice Allison\")\n              .hasRow(\"bob\", \"Bob Bobberson\")\n              .hasRow(\"eve\", \"Eve Evenson\")\n              .isExhausted();\n    } finally {\n      transaction.end();\n    }\n  }\n\n  @Test public void synchronousQueryWithSupportSQLiteQueryDuringTransactionSeesChanges() {\n    Transaction transaction = db.newTransaction();\n    try {\n      assertCursor(db.query(new SimpleSQLiteQuery(SELECT_EMPLOYEES)))\n              .hasRow(\"alice\", \"Alice Allison\")\n              .hasRow(\"bob\", \"Bob Bobberson\")\n              .hasRow(\"eve\", \"Eve Evenson\")\n              .isExhausted();\n\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n\n      assertCursor(db.query(new SimpleSQLiteQuery(SELECT_EMPLOYEES)))\n              .hasRow(\"alice\", \"Alice Allison\")\n              .hasRow(\"bob\", \"Bob Bobberson\")\n              .hasRow(\"eve\", \"Eve Evenson\")\n              .hasRow(\"john\", \"John Johnson\")\n              .hasRow(\"nick\", \"Nick Nickers\")\n              .isExhausted();\n\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n  }\n\n  @Test public void nestedTransactionsOnlyNotifyOnce() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transactionOuter = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n\n      Transaction transactionInner = db.newTransaction();\n      try {\n        db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n        transactionInner.markSuccessful();\n      } finally {\n        transactionInner.end();\n      }\n\n      transactionOuter.markSuccessful();\n    } finally {\n      transactionOuter.end();\n    }\n\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .hasRow(\"john\", \"John Johnson\")\n        .hasRow(\"nick\", \"Nick Nickers\")\n        .isExhausted();\n  }\n\n  @Test public void nestedTransactionsOnMultipleTables() {\n    db.createQuery(BOTH_TABLES, SELECT_MANAGER_LIST).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .isExhausted();\n\n    Transaction transactionOuter = db.newTransaction();\n    try {\n\n      Transaction transactionInner = db.newTransaction();\n      try {\n        db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n        transactionInner.markSuccessful();\n      } finally {\n        transactionInner.end();\n      }\n\n      transactionInner = db.newTransaction();\n      try {\n        db.insert(TABLE_MANAGER, CONFLICT_NONE, manager(testDb.aliceId, testDb.bobId));\n        transactionInner.markSuccessful();\n      } finally {\n        transactionInner.end();\n      }\n\n      transactionOuter.markSuccessful();\n    } finally {\n      transactionOuter.end();\n    }\n\n    o.assertCursor()\n        .hasRow(\"Eve Evenson\", \"Alice Allison\")\n        .hasRow(\"Alice Allison\", \"Bob Bobberson\")\n        .isExhausted();\n  }\n\n  @Test public void emptyTransactionDoesNotNotify() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transaction = db.newTransaction();\n    try {\n      transaction.markSuccessful();\n    } finally {\n      transaction.end();\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @Test public void transactionRollbackDoesNotNotify() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES).subscribe(o);\n    o.assertCursor()\n        .hasRow(\"alice\", \"Alice Allison\")\n        .hasRow(\"bob\", \"Bob Bobberson\")\n        .hasRow(\"eve\", \"Eve Evenson\")\n        .isExhausted();\n\n    Transaction transaction = db.newTransaction();\n    try {\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"nick\", \"Nick Nickers\"));\n      // No call to set successful.\n    } finally {\n      transaction.end();\n    }\n    o.assertNoMoreEvents();\n  }\n\n  @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.HONEYCOMB)\n  @Test public void nonExclusiveTransactionWorks() throws InterruptedException {\n    final CountDownLatch transactionStarted = new CountDownLatch(1);\n    final CountDownLatch transactionProceed = new CountDownLatch(1);\n    final CountDownLatch transactionCompleted = new CountDownLatch(1);\n\n    new Thread() {\n      @Override public void run() {\n        Transaction transaction = db.newNonExclusiveTransaction();\n        transactionStarted.countDown();\n        try {\n          db.insert(TABLE_EMPLOYEE, CONFLICT_NONE, employee(\"hans\", \"Hans Hanson\"));\n          transactionProceed.await(10, SECONDS);\n        } catch (InterruptedException e) {\n          throw new RuntimeException(\"Exception in transaction thread\", e);\n        }\n        transaction.markSuccessful();\n        transaction.close();\n        transactionCompleted.countDown();\n      }\n    }.start();\n\n    assertThat(transactionStarted.await(10, SECONDS)).isTrue();\n\n    //Simple query\n    Employee employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n            .lift(Query.mapToOne(Employee.MAPPER))\n            .blockingFirst();\n    assertThat(employees).isEqualTo(new Employee(\"alice\", \"Alice Allison\"));\n\n    transactionProceed.countDown();\n    assertThat(transactionCompleted.await(10, SECONDS)).isTrue();\n  }\n\n  @Test public void badQueryThrows() {\n    try {\n      //noinspection CheckResult\n      db.query(\"SELECT * FROM missing\");\n      fail();\n    } catch (SQLiteException e) {\n      assertThat(e.getMessage()).contains(\"no such table: missing\");\n    }\n  }\n\n  @Test public void badInsertThrows() {\n    try {\n      db.insert(\"missing\", CONFLICT_NONE, employee(\"john\", \"John Johnson\"));\n      fail();\n    } catch (SQLiteException e) {\n      assertThat(e.getMessage()).contains(\"no such table: missing\");\n    }\n  }\n\n  @Test public void badUpdateThrows() {\n    try {\n      db.update(\"missing\", CONFLICT_NONE, employee(\"john\", \"John Johnson\"), \"1\");\n      fail();\n    } catch (SQLiteException e) {\n      assertThat(e.getMessage()).contains(\"no such table: missing\");\n    }\n  }\n\n  @Test public void badDeleteThrows() {\n    try {\n      db.delete(\"missing\", \"1\");\n      fail();\n    } catch (SQLiteException e) {\n      assertThat(e.getMessage()).contains(\"no such table: missing\");\n    }\n  }\n\n  private static CursorAssert assertCursor(Cursor cursor) {\n    return new CursorAssert(cursor);\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/QueryObservableTest.java",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.database.MatrixCursor;\nimport com.squareup.sqlbrite3.QueryObservable;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.Observable;\nimport io.reactivex.functions.Function;\nimport org.junit.Test;\n\npublic final class QueryObservableTest {\n  @Test public void mapToListThrowsFromQueryRun() {\n    final IllegalStateException error = new IllegalStateException(\"test exception\");\n    Query query = new Query() {\n      @Override public Cursor run() {\n        throw error;\n      }\n    };\n    new QueryObservable(Observable.just(query)) //\n        .mapToList(new Function<Cursor, Object>() {\n          @Override public Object apply(Cursor cursor) {\n            throw new AssertionError(\"Must not be called\");\n          }\n        }) //\n        .test() //\n        .assertNoValues() //\n        .assertError(error);\n  }\n\n  @Test public void mapToListThrowsFromMapFunction() {\n    Query query = new Query() {\n      @Override public Cursor run() {\n        MatrixCursor cursor = new MatrixCursor(new String[] { \"col1\" });\n        cursor.addRow(new Object[] { \"value1\" });\n        return cursor;\n      }\n    };\n\n    final IllegalStateException error = new IllegalStateException(\"test exception\");\n    new QueryObservable(Observable.just(query)) //\n        .mapToList(new Function<Cursor, Object>() {\n          @Override public Object apply(Cursor cursor) {\n            throw error;\n          }\n        }) //\n        .test() //\n        .assertNoValues() //\n        .assertError(error);\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/QueryTest.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Configuration;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Factory;\nimport android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;\nimport android.database.Cursor;\nimport android.os.Build;\nimport android.support.annotation.Nullable;\nimport android.support.test.InstrumentationRegistry;\nimport android.support.test.filters.SdkSuppress;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport com.squareup.sqlbrite3.TestDb.Employee;\nimport io.reactivex.Observable;\nimport io.reactivex.functions.Function;\nimport io.reactivex.observers.TestObserver;\nimport io.reactivex.schedulers.Schedulers;\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.squareup.sqlbrite3.TestDb.Employee.MAPPER;\nimport static com.squareup.sqlbrite3.TestDb.SELECT_EMPLOYEES;\nimport static com.squareup.sqlbrite3.TestDb.TABLE_EMPLOYEE;\nimport static org.junit.Assert.fail;\n\npublic final class QueryTest {\n  private BriteDatabase db;\n\n  @Before public void setUp() {\n    Configuration configuration = Configuration.builder(InstrumentationRegistry.getContext())\n        .callback(new TestDb())\n        .build();\n\n    Factory factory = new FrameworkSQLiteOpenHelperFactory();\n    SupportSQLiteOpenHelper helper = factory.create(configuration);\n\n    SqlBrite sqlBrite = new SqlBrite.Builder().build();\n    db = sqlBrite.wrapDatabaseHelper(helper, Schedulers.trampoline());\n  }\n\n  @Test public void mapToOne() {\n    Employee employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOne(MAPPER))\n        .blockingFirst();\n    assertThat(employees).isEqualTo(new Employee(\"alice\", \"Alice Allison\"));\n  }\n\n  @Test public void mapToOneThrowsWhenMapperReturnsNull() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOne(new Function<Cursor, Employee>() {\n          @Override public Employee apply(Cursor cursor) throws Exception {\n            return null;\n          }\n        }))\n        .test()\n        .assertError(NullPointerException.class)\n        .assertErrorMessage(\"QueryToOne mapper returned null\");\n  }\n\n  @Test public void mapToOneThrowsOnMultipleRows() {\n    Observable<Employee> employees =\n        db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 2\") //\n            .lift(Query.mapToOne(MAPPER));\n    try {\n      employees.blockingFirst();\n      fail();\n    } catch (IllegalStateException e) {\n      assertThat(e).hasMessage(\"Cursor returned more than 1 row\");\n    }\n  }\n\n  @Test public void mapToOneIgnoresNullCursor() {\n    Query nully = new Query() {\n      @Nullable @Override public Cursor run() {\n        return null;\n      }\n    };\n\n    TestObserver<Employee> observer = new TestObserver<>();\n    Observable.just(nully)\n        .lift(Query.mapToOne(MAPPER))\n        .subscribe(observer);\n\n    observer.assertNoValues();\n    observer.assertComplete();\n  }\n\n  @Test public void mapToOneOrDefault() {\n    Employee employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOneOrDefault(\n            MAPPER, new Employee(\"fred\", \"Fred Frederson\")))\n        .blockingFirst();\n    assertThat(employees).isEqualTo(new Employee(\"alice\", \"Alice Allison\"));\n  }\n\n  @Test public void mapToOneOrDefaultDisallowsNullDefault() {\n    try {\n      Query.mapToOneOrDefault(MAPPER, null);\n      fail();\n    } catch (NullPointerException e) {\n      assertThat(e).hasMessage(\"defaultValue == null\");\n    }\n  }\n\n  @Test public void mapToOneOrDefaultThrowsWhenMapperReturnsNull() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOneOrDefault(new Function<Cursor, Employee>() {\n          @Override public Employee apply(Cursor cursor) throws Exception {\n            return null;\n          }\n        }, new Employee(\"fred\", \"Fred Frederson\")))\n        .test()\n        .assertError(NullPointerException.class)\n        .assertErrorMessage(\"QueryToOne mapper returned null\");\n  }\n\n  @Test public void mapToOneOrDefaultThrowsOnMultipleRows() {\n    Observable<Employee> employees =\n        db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 2\") //\n            .lift(Query.mapToOneOrDefault(\n                MAPPER, new Employee(\"fred\", \"Fred Frederson\")));\n    try {\n      employees.blockingFirst();\n      fail();\n    } catch (IllegalStateException e) {\n      assertThat(e).hasMessage(\"Cursor returned more than 1 row\");\n    }\n  }\n\n  @Test public void mapToOneOrDefaultReturnsDefaultWhenNullCursor() {\n    Employee defaultEmployee = new Employee(\"bob\", \"Bob Bobberson\");\n    Query nully = new Query() {\n      @Nullable @Override public Cursor run() {\n        return null;\n      }\n    };\n\n    TestObserver<Employee> observer = new TestObserver<>();\n    Observable.just(nully)\n        .lift(Query.mapToOneOrDefault(MAPPER, defaultEmployee))\n        .subscribe(observer);\n\n    observer.assertValues(defaultEmployee);\n    observer.assertComplete();\n  }\n\n  @Test public void mapToList() {\n    List<Employee> employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES)\n        .lift(Query.mapToList(MAPPER))\n        .blockingFirst();\n    assertThat(employees).containsExactly( //\n        new Employee(\"alice\", \"Alice Allison\"), //\n        new Employee(\"bob\", \"Bob Bobberson\"), //\n        new Employee(\"eve\", \"Eve Evenson\"));\n  }\n\n  @Test public void mapToListEmptyWhenNoRows() {\n    List<Employee> employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" WHERE 1=2\")\n        .lift(Query.mapToList(MAPPER))\n        .blockingFirst();\n    assertThat(employees).isEmpty();\n  }\n\n  @Test public void mapToListReturnsNullOnMapperNull() {\n    Function<Cursor, Employee> mapToNull = new Function<Cursor, Employee>() {\n      private int count;\n\n      @Override public Employee apply(Cursor cursor) throws Exception {\n        return count++ == 2 ? null : MAPPER.apply(cursor);\n      }\n    };\n    List<Employee> employees = db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES) //\n            .lift(Query.mapToList(mapToNull)) //\n            .blockingFirst();\n\n    assertThat(employees).containsExactly(\n        new Employee(\"alice\", \"Alice Allison\"),\n        new Employee(\"bob\", \"Bob Bobberson\"),\n        null);\n  }\n\n  @Test public void mapToListIgnoresNullCursor() {\n    Query nully = new Query() {\n      @Nullable @Override public Cursor run() {\n        return null;\n      }\n    };\n\n    TestObserver<List<Employee>> subscriber = new TestObserver<>();\n    Observable.just(nully)\n        .lift(Query.mapToList(MAPPER))\n        .subscribe(subscriber);\n\n    subscriber.assertNoValues();\n    subscriber.assertComplete();\n  }\n\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)\n  @Test public void mapToOptional() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOptional(MAPPER))\n        .test()\n        .assertValue(Optional.of(new Employee(\"alice\", \"Alice Allison\")));\n  }\n\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)\n  @Test public void mapToOptionalThrowsWhenMapperReturnsNull() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 1\")\n        .lift(Query.mapToOptional(new Function<Cursor, Employee>() {\n          @Override public Employee apply(Cursor cursor) throws Exception {\n            return null;\n          }\n        }))\n        .test()\n        .assertError(NullPointerException.class)\n        .assertErrorMessage(\"QueryToOne mapper returned null\");\n  }\n\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)\n  @Test public void mapToOptionalThrowsOnMultipleRows() {\n    db.createQuery(TABLE_EMPLOYEE, SELECT_EMPLOYEES + \" LIMIT 2\") //\n        .lift(Query.mapToOptional(MAPPER))\n        .test()\n        .assertError(IllegalStateException.class)\n        .assertErrorMessage(\"Cursor returned more than 1 row\");\n  }\n\n  @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)\n  @Test public void mapToOptionalIgnoresNullCursor() {\n    Query nully = new Query() {\n      @Nullable @Override public Cursor run() {\n        return null;\n      }\n    };\n\n    Observable.just(nully)\n        .lift(Query.mapToOptional(MAPPER))\n        .test()\n        .assertValue(Optional.<Employee>empty());\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/RecordingObserver.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.util.Log;\nimport io.reactivex.observers.DisposableObserver;\nimport java.util.concurrent.BlockingDeque;\nimport java.util.concurrent.LinkedBlockingDeque;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static com.squareup.sqlbrite3.SqlBrite.Query;\n\nclass RecordingObserver extends DisposableObserver<Query> {\n  private static final Object COMPLETED = \"<completed>\";\n  private static final String TAG = RecordingObserver.class.getSimpleName();\n\n  final BlockingDeque<Object> events = new LinkedBlockingDeque<>();\n\n  @Override public final void onComplete() {\n    Log.d(TAG, \"onCompleted\");\n    events.add(COMPLETED);\n  }\n\n  @Override public final void onError(Throwable e) {\n    Log.d(TAG, \"onError \" + e.getClass().getSimpleName() + \" \" + e.getMessage());\n    events.add(e);\n  }\n\n  @Override public final void onNext(Query value) {\n    Log.d(TAG, \"onNext \" + value);\n    events.add(value.run());\n  }\n\n  protected Object takeEvent() {\n    Object item = events.removeFirst();\n    if (item == null) {\n      throw new AssertionError(\"No items.\");\n    }\n    return item;\n  }\n\n  public final CursorAssert assertCursor() {\n    Object event = takeEvent();\n    assertThat(event).isInstanceOf(Cursor.class);\n    return new CursorAssert((Cursor) event);\n  }\n\n  public final void assertErrorContains(String expected) {\n    Object event = takeEvent();\n    assertThat(event).isInstanceOf(Throwable.class);\n    assertThat(((Throwable) event).getMessage()).contains(expected);\n  }\n\n  public final void assertIsCompleted() {\n    Object event = takeEvent();\n    assertThat(event).isEqualTo(COMPLETED);\n  }\n\n  public void assertNoMoreEvents() {\n    assertThat(events).isEmpty();\n  }\n\n  static final class CursorAssert {\n    private final Cursor cursor;\n    private int row = 0;\n\n    CursorAssert(Cursor cursor) {\n      this.cursor = cursor;\n    }\n\n    public CursorAssert hasRow(Object... values) {\n      assertThat(cursor.moveToNext()).named(\"row \" + (row + 1) + \" exists\").isTrue();\n      row += 1;\n      assertThat(cursor.getColumnCount()).named(\"column count\").isEqualTo(values.length);\n      for (int i = 0; i < values.length; i++) {\n        assertThat(cursor.getString(i))\n            .named(\"row \" + row + \" column '\" + cursor.getColumnName(i) + \"'\")\n            .isEqualTo(values[i]);\n      }\n      return this;\n    }\n\n    public void isExhausted() {\n      if (cursor.moveToNext()) {\n        StringBuilder data = new StringBuilder();\n        for (int i = 0; i < cursor.getColumnCount(); i++) {\n          if (i > 0) data.append(\", \");\n          data.append(cursor.getString(i));\n        }\n        throw new AssertionError(\"Expected no more rows but was: \" + data);\n      }\n      cursor.close();\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/SqlBriteTest.java",
    "content": "package com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.database.MatrixCursor;\nimport android.support.annotation.Nullable;\nimport android.support.test.runner.AndroidJUnit4;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.functions.Function;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport static com.google.common.truth.Truth.assertThat;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\n@SuppressWarnings(\"CheckResult\")\npublic final class SqlBriteTest {\n  private static final String FIRST_NAME = \"first_name\";\n  private static final String LAST_NAME = \"last_name\";\n  private static final String[] COLUMN_NAMES = { FIRST_NAME, LAST_NAME };\n\n  @Test public void builderDisallowsNull() {\n    SqlBrite.Builder builder = new SqlBrite.Builder();\n    try {\n      builder.logger(null);\n      fail();\n    } catch (NullPointerException e) {\n      assertThat(e).hasMessage(\"logger == null\");\n    }\n    try {\n      builder.queryTransformer(null);\n      fail();\n    } catch (NullPointerException e) {\n      assertThat(e).hasMessage(\"queryTransformer == null\");\n    }\n  }\n\n  @Test public void asRowsEmpty() {\n    MatrixCursor cursor = new MatrixCursor(COLUMN_NAMES);\n    Query query = new CursorQuery(cursor);\n    List<Name> names = query.asRows(Name.MAP).toList().blockingGet();\n    assertThat(names).isEmpty();\n  }\n\n  @Test public void asRows() {\n    MatrixCursor cursor = new MatrixCursor(COLUMN_NAMES);\n    cursor.addRow(new Object[] { \"Alice\", \"Allison\" });\n    cursor.addRow(new Object[] { \"Bob\", \"Bobberson\" });\n\n    Query query = new CursorQuery(cursor);\n    List<Name> names = query.asRows(Name.MAP).toList().blockingGet();\n    assertThat(names).containsExactly(new Name(\"Alice\", \"Allison\"), new Name(\"Bob\", \"Bobberson\"));\n  }\n\n  @Test public void asRowsStopsWhenUnsubscribed() {\n    MatrixCursor cursor = new MatrixCursor(COLUMN_NAMES);\n    cursor.addRow(new Object[] { \"Alice\", \"Allison\" });\n    cursor.addRow(new Object[] { \"Bob\", \"Bobberson\" });\n\n    Query query = new CursorQuery(cursor);\n    final AtomicInteger count = new AtomicInteger();\n    query.asRows(new Function<Cursor, Name>() {\n      @Override public Name apply(Cursor cursor) throws Exception {\n        count.incrementAndGet();\n        return Name.MAP.apply(cursor);\n      }\n    }).take(1).blockingFirst();\n    assertThat(count.get()).isEqualTo(1);\n  }\n\n  @Test public void asRowsEmptyWhenNullCursor() {\n    Query nully = new Query() {\n      @Nullable @Override public Cursor run() {\n        return null;\n      }\n    };\n\n    final AtomicInteger count = new AtomicInteger();\n    nully.asRows(new Function<Cursor, Name>() {\n      @Override public Name apply(Cursor cursor) throws Exception {\n        count.incrementAndGet();\n        return Name.MAP.apply(cursor);\n      }\n    }).test().assertNoValues().assertComplete();\n\n    assertThat(count.get()).isEqualTo(0);\n  }\n\n  static final class Name {\n    static final Function<Cursor, Name> MAP = new Function<Cursor, Name>() {\n      @Override public Name apply(Cursor cursor) {\n        return new Name( //\n            cursor.getString(cursor.getColumnIndexOrThrow(FIRST_NAME)),\n            cursor.getString(cursor.getColumnIndexOrThrow(LAST_NAME)));\n      }\n    };\n\n    final String first;\n    final String last;\n\n    Name(String first, String last) {\n      this.first = first;\n      this.last = last;\n    }\n\n    @Override public boolean equals(Object o) {\n      if (o == this) return true;\n      if (!(o instanceof Name)) return false;\n      Name other = (Name) o;\n      return first.equals(other.first) && last.equals(other.last);\n    }\n\n    @Override public int hashCode() {\n      return first.hashCode() * 17 + last.hashCode();\n    }\n\n    @Override public String toString() {\n      return \"Name[\" + first + ' ' + last + ']';\n    }\n  }\n\n  static final class CursorQuery extends Query {\n    private final Cursor cursor;\n\n    CursorQuery(Cursor cursor) {\n      this.cursor = cursor;\n    }\n\n    @Override public Cursor run() {\n      return cursor;\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/TestDb.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.arch.persistence.db.SupportSQLiteDatabase;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.support.annotation.NonNull;\nimport io.reactivex.functions.Function;\nimport java.util.Arrays;\nimport java.util.Collection;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;\nimport static com.squareup.sqlbrite3.TestDb.EmployeeTable.ID;\nimport static com.squareup.sqlbrite3.TestDb.EmployeeTable.NAME;\nimport static com.squareup.sqlbrite3.TestDb.EmployeeTable.USERNAME;\nimport static com.squareup.sqlbrite3.TestDb.ManagerTable.EMPLOYEE_ID;\nimport static com.squareup.sqlbrite3.TestDb.ManagerTable.MANAGER_ID;\n\nfinal class TestDb extends SupportSQLiteOpenHelper.Callback {\n  static final String TABLE_EMPLOYEE = \"employee\";\n  static final String TABLE_MANAGER = \"manager\";\n\n  static final String SELECT_EMPLOYEES =\n      \"SELECT \" + USERNAME + \", \" + NAME + \" FROM \" + TABLE_EMPLOYEE;\n  static final String SELECT_MANAGER_LIST = \"\"\n      + \"SELECT e.\" + NAME + \", m.\" + NAME + \" \"\n      + \"FROM \" + TABLE_MANAGER + \" AS manager \"\n      + \"JOIN \" + TABLE_EMPLOYEE + \" AS e \"\n      + \"ON manager.\" + EMPLOYEE_ID + \" = e.\" + ID + \" \"\n      + \"JOIN \" + TABLE_EMPLOYEE + \" as m \"\n      + \"ON manager.\" + MANAGER_ID + \" = m.\" + ID;\n  static final Collection<String> BOTH_TABLES =\n      Arrays.asList(TABLE_EMPLOYEE, TABLE_MANAGER);\n\n  interface EmployeeTable {\n    String ID = \"_id\";\n    String USERNAME = \"username\";\n    String NAME = \"name\";\n  }\n\n  static final class Employee {\n    static final Function<Cursor, Employee> MAPPER = new Function<Cursor, Employee>() {\n      @Override public Employee apply(Cursor cursor) {\n        return new Employee( //\n            cursor.getString(cursor.getColumnIndexOrThrow(EmployeeTable.USERNAME)),\n            cursor.getString(cursor.getColumnIndexOrThrow(EmployeeTable.NAME)));\n      }\n    };\n\n    final String username;\n    final String name;\n\n    Employee(String username, String name) {\n      this.username = username;\n      this.name = name;\n    }\n\n    @Override public boolean equals(Object o) {\n      if (o == this) return true;\n      if (!(o instanceof Employee)) return false;\n      Employee other = (Employee) o;\n      return username.equals(other.username) && name.equals(other.name);\n    }\n\n    @Override public int hashCode() {\n      return username.hashCode() * 17 + name.hashCode();\n    }\n\n    @Override public String toString() {\n      return \"Employee[\" + username + ' ' + name + ']';\n    }\n  }\n\n  interface ManagerTable {\n    String ID = \"_id\";\n    String EMPLOYEE_ID = \"employee_id\";\n    String MANAGER_ID = \"manager_id\";\n  }\n\n  private static final String CREATE_EMPLOYEE = \"CREATE TABLE \" + TABLE_EMPLOYEE + \" (\"\n      + EmployeeTable.ID + \" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"\n      + EmployeeTable.USERNAME + \" TEXT NOT NULL UNIQUE, \"\n      + EmployeeTable.NAME + \" TEXT NOT NULL)\";\n  private static final String CREATE_MANAGER = \"CREATE TABLE \" + TABLE_MANAGER + \" (\"\n      + ManagerTable.ID + \" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \"\n      + ManagerTable.EMPLOYEE_ID + \" INTEGER NOT NULL UNIQUE REFERENCES \" + TABLE_EMPLOYEE + \"(\" + EmployeeTable.ID + \"), \"\n      + ManagerTable.MANAGER_ID + \" INTEGER NOT NULL REFERENCES \" + TABLE_EMPLOYEE + \"(\" + EmployeeTable.ID + \"))\";\n\n  long aliceId;\n  long bobId;\n  long eveId;\n\n  TestDb() {\n    super(1);\n  }\n\n  @Override public void onCreate(@NonNull SupportSQLiteDatabase db) {\n    db.execSQL(\"PRAGMA foreign_keys=ON\");\n\n    db.execSQL(CREATE_EMPLOYEE);\n    aliceId = db.insert(TABLE_EMPLOYEE, CONFLICT_FAIL, employee(\"alice\", \"Alice Allison\"));\n    bobId = db.insert(TABLE_EMPLOYEE, CONFLICT_FAIL, employee(\"bob\", \"Bob Bobberson\"));\n    eveId = db.insert(TABLE_EMPLOYEE, CONFLICT_FAIL, employee(\"eve\", \"Eve Evenson\"));\n\n    db.execSQL(CREATE_MANAGER);\n    db.insert(TABLE_MANAGER, CONFLICT_FAIL, manager(eveId, aliceId));\n  }\n\n  static ContentValues employee(String username, String name) {\n    ContentValues values = new ContentValues();\n    values.put(EmployeeTable.USERNAME, username);\n    values.put(EmployeeTable.NAME, name);\n    return values;\n  }\n\n  static ContentValues manager(long employeeId, long managerId) {\n    ContentValues values = new ContentValues();\n    values.put(ManagerTable.EMPLOYEE_ID, employeeId);\n    values.put(ManagerTable.MANAGER_ID, managerId);\n    return values;\n  }\n\n  @Override\n  public void onUpgrade(@NonNull SupportSQLiteDatabase db, int oldVersion, int newVersion) {\n    throw new AssertionError();\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/androidTest/java/com/squareup/sqlbrite3/TestScheduler.java",
    "content": "/*\n * Copyright (C) 2016 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport io.reactivex.Scheduler;\nimport io.reactivex.annotations.NonNull;\nimport io.reactivex.disposables.Disposable;\nimport java.util.concurrent.TimeUnit;\n\nfinal class TestScheduler extends Scheduler {\n  private final io.reactivex.schedulers.TestScheduler delegate =\n      new io.reactivex.schedulers.TestScheduler();\n\n  private boolean runTasksImmediately = true;\n\n  public void runTasksImmediately(boolean runTasksImmediately) {\n    this.runTasksImmediately = runTasksImmediately;\n  }\n\n  public void triggerActions() {\n    delegate.triggerActions();\n  }\n\n  @Override public Worker createWorker() {\n    return new TestWorker();\n  }\n\n  class TestWorker extends Worker {\n    private final Worker delegateWorker = delegate.createWorker();\n\n    @Override\n    public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {\n      Disposable disposable = delegateWorker.schedule(run, delay, unit);\n      if (runTasksImmediately) {\n        triggerActions();\n      }\n      return disposable;\n    }\n\n    @Override public void dispose() {\n      delegateWorker.dispose();\n    }\n\n    @Override public boolean isDisposed() {\n      return delegateWorker.isDisposed();\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.squareup.sqlbrite2\"/>\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/BriteContentResolver.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.content.ContentResolver;\nimport android.database.ContentObserver;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.support.annotation.CheckResult;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport com.squareup.sqlbrite3.SqlBrite.Logger;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableEmitter;\nimport io.reactivex.ObservableOnSubscribe;\nimport io.reactivex.ObservableTransformer;\nimport io.reactivex.Scheduler;\nimport io.reactivex.functions.Cancellable;\nimport java.util.Arrays;\n\nimport static com.squareup.sqlbrite3.QueryObservable.QUERY_OBSERVABLE;\nimport static java.lang.System.nanoTime;\nimport static java.util.concurrent.TimeUnit.NANOSECONDS;\n\n/**\n * A lightweight wrapper around {@link ContentResolver} which allows for continuously observing\n * the result of a query. Create using a {@link SqlBrite} instance.\n */\npublic final class BriteContentResolver {\n  final Handler contentObserverHandler = new Handler(Looper.getMainLooper());\n\n  final ContentResolver contentResolver;\n  private final Logger logger;\n  private final Scheduler scheduler;\n  private final ObservableTransformer<Query, Query> queryTransformer;\n\n  volatile boolean logging;\n\n  BriteContentResolver(ContentResolver contentResolver, Logger logger, Scheduler scheduler,\n      ObservableTransformer<Query, Query> queryTransformer) {\n    this.contentResolver = contentResolver;\n    this.logger = logger;\n    this.scheduler = scheduler;\n    this.queryTransformer = queryTransformer;\n  }\n\n  /** Control whether debug logging is enabled. */\n  public void setLoggingEnabled(boolean enabled) {\n    logging = enabled;\n  }\n\n  /**\n   * Create an observable which will notify subscribers with a {@linkplain Query query} for\n   * execution. Subscribers are responsible for <b>always</b> closing {@link Cursor} instance\n   * returned from the {@link Query}.\n   * <p>\n   * Subscribers will receive an immediate notification for initial data as well as subsequent\n   * notifications for when the supplied {@code uri}'s data changes. Unsubscribe when you no longer\n   * want updates to a query.\n   * <p>\n   * Since content resolver triggers are inherently asynchronous, items emitted from the returned\n   * observable use the {@link Scheduler} supplied to {@link SqlBrite#wrapContentProvider}. For\n   * consistency, the immediate notification sent on subscribe also uses this scheduler. As such,\n   * calling {@link Observable#subscribeOn subscribeOn} on the returned observable has no effect.\n   * <p>\n   * Note: To skip the immediate notification and only receive subsequent notifications when data\n   * has changed call {@code skip(1)} on the returned observable.\n   * <p>\n   * <b>Warning:</b> this method does not perform the query! Only by subscribing to the returned\n   * {@link Observable} will the operation occur.\n   *\n   * @see ContentResolver#query(Uri, String[], String, String[], String)\n   * @see ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)\n   */\n  @CheckResult @NonNull\n  public QueryObservable createQuery(@NonNull final Uri uri, @Nullable final String[] projection,\n      @Nullable final String selection, @Nullable final String[] selectionArgs, @Nullable\n      final String sortOrder, final boolean notifyForDescendents) {\n    final Query query = new Query() {\n      @Override public Cursor run() {\n        long startNanos = nanoTime();\n        Cursor cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder);\n\n        if (logging) {\n          long tookMillis = NANOSECONDS.toMillis(nanoTime() - startNanos);\n          log(\"QUERY (%sms)\\n  uri: %s\\n  projection: %s\\n  selection: %s\\n  selectionArgs: %s\\n  \"\n                  + \"sortOrder: %s\\n  notifyForDescendents: %s\", tookMillis, uri,\n              Arrays.toString(projection), selection, Arrays.toString(selectionArgs), sortOrder,\n              notifyForDescendents);\n        }\n\n        return cursor;\n      }\n    };\n    Observable<Query> queries = Observable.create(new ObservableOnSubscribe<Query>() {\n      @Override public void subscribe(final ObservableEmitter<Query> e) throws Exception {\n        final ContentObserver observer = new ContentObserver(contentObserverHandler) {\n          @Override public void onChange(boolean selfChange) {\n            if (!e.isDisposed()) {\n              e.onNext(query);\n            }\n          }\n        };\n        contentResolver.registerContentObserver(uri, notifyForDescendents, observer);\n        e.setCancellable(new Cancellable() {\n          @Override public void cancel() throws Exception {\n            contentResolver.unregisterContentObserver(observer);\n          }\n        });\n\n        if (!e.isDisposed()) {\n          e.onNext(query); // Trigger initial query.\n        }\n      }\n    });\n    return queries //\n        .observeOn(scheduler) //\n        .compose(queryTransformer) // Apply the user's query transformer.\n        .to(QUERY_OBSERVABLE);\n  }\n\n  void log(String message, Object... args) {\n    if (args.length > 0) message = String.format(message, args);\n    logger.log(message);\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/BriteDatabase.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.arch.persistence.db.SimpleSQLiteQuery;\nimport android.arch.persistence.db.SupportSQLiteDatabase;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.arch.persistence.db.SupportSQLiteOpenHelper.Callback;\nimport android.arch.persistence.db.SupportSQLiteQuery;\nimport android.arch.persistence.db.SupportSQLiteStatement;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteTransactionListener;\nimport android.support.annotation.CheckResult;\nimport android.support.annotation.IntDef;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.WorkerThread;\nimport com.squareup.sqlbrite3.SqlBrite.Logger;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableTransformer;\nimport io.reactivex.Scheduler;\nimport io.reactivex.functions.Consumer;\nimport io.reactivex.functions.Function;\nimport io.reactivex.functions.Predicate;\nimport io.reactivex.subjects.PublishSubject;\nimport io.reactivex.subjects.Subject;\nimport java.io.Closeable;\nimport java.lang.annotation.Retention;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_ABORT;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_NONE;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE;\nimport static android.database.sqlite.SQLiteDatabase.CONFLICT_ROLLBACK;\nimport static com.squareup.sqlbrite3.QueryObservable.QUERY_OBSERVABLE;\nimport static java.lang.annotation.RetentionPolicy.SOURCE;\nimport static java.util.Collections.singletonList;\n\n/**\n * A lightweight wrapper around {@link SupportSQLiteOpenHelper} which allows for continuously\n * observing the result of a query. Create using a {@link SqlBrite} instance.\n */\npublic final class BriteDatabase implements Closeable {\n  private final SupportSQLiteOpenHelper helper;\n  private final Logger logger;\n  private final ObservableTransformer<Query, Query> queryTransformer;\n\n  // Package-private to avoid synthetic accessor method for 'transaction' instance.\n  final ThreadLocal<SqliteTransaction> transactions = new ThreadLocal<>();\n  private final Subject<Set<String>> triggers = PublishSubject.create();\n\n  private final Transaction transaction = new Transaction() {\n    @Override public void markSuccessful() {\n      if (logging) log(\"TXN SUCCESS %s\", transactions.get());\n      getWritableDatabase().setTransactionSuccessful();\n    }\n\n    @Override public boolean yieldIfContendedSafely() {\n      return getWritableDatabase().yieldIfContendedSafely();\n    }\n\n    @Override public boolean yieldIfContendedSafely(long sleepAmount, TimeUnit sleepUnit) {\n      return getWritableDatabase().yieldIfContendedSafely(sleepUnit.toMillis(sleepAmount));\n    }\n\n    @Override public void end() {\n      SqliteTransaction transaction = transactions.get();\n      if (transaction == null) {\n        throw new IllegalStateException(\"Not in transaction.\");\n      }\n      SqliteTransaction newTransaction = transaction.parent;\n      transactions.set(newTransaction);\n      if (logging) log(\"TXN END %s\", transaction);\n      getWritableDatabase().endTransaction();\n      // Send the triggers after ending the transaction in the DB.\n      if (transaction.commit) {\n        sendTableTrigger(transaction);\n      }\n    }\n\n    @Override public void close() {\n      end();\n    }\n  };\n  private final Consumer<Object> ensureNotInTransaction = new Consumer<Object>() {\n    @Override public void accept(Object ignored) throws Exception {\n      if (transactions.get() != null) {\n        throw new IllegalStateException(\"Cannot subscribe to observable query in a transaction.\");\n      }\n    }\n  };\n\n  private final Scheduler scheduler;\n\n  // Package-private to avoid synthetic accessor method for 'transaction' instance.\n  volatile boolean logging;\n\n  BriteDatabase(SupportSQLiteOpenHelper helper, Logger logger, Scheduler scheduler,\n      ObservableTransformer<Query, Query> queryTransformer) {\n    this.helper = helper;\n    this.logger = logger;\n    this.scheduler = scheduler;\n    this.queryTransformer = queryTransformer;\n  }\n\n  /**\n   * Control whether debug logging is enabled.\n   */\n  public void setLoggingEnabled(boolean enabled) {\n    logging = enabled;\n  }\n\n  /**\n   * Create and/or open a database.  This will be the same object returned by\n   * {@link SupportSQLiteOpenHelper#getWritableDatabase} unless some problem, such as a full disk,\n   * requires the database to be opened read-only.  In that case, a read-only\n   * database object will be returned.  If the problem is fixed, a future call\n   * to {@link SupportSQLiteOpenHelper#getWritableDatabase} may succeed, in which case the read-only\n   * database object will be closed and the read/write object will be returned\n   * in the future.\n   *\n   * <p class=\"caution\">Like {@link SupportSQLiteOpenHelper#getWritableDatabase}, this method may\n   * take a long time to return, so you should not call it from the\n   * application main thread, including from\n   * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.\n   *\n   * @throws android.database.sqlite.SQLiteException if the database cannot be opened\n   * @return a database object valid until {@link SupportSQLiteOpenHelper#getWritableDatabase}\n   *     or {@link #close} is called.\n   */\n  @NonNull @CheckResult @WorkerThread\n  public SupportSQLiteDatabase getReadableDatabase() {\n    return helper.getReadableDatabase();\n  }\n\n  /**\n   * Create and/or open a database that will be used for reading and writing.\n   * The first time this is called, the database will be opened and\n   * {@link Callback#onCreate}, {@link Callback#onUpgrade} and/or {@link Callback#onOpen} will be\n   * called.\n   *\n   * <p>Once opened successfully, the database is cached, so you can\n   * call this method every time you need to write to the database.\n   * (Make sure to call {@link #close} when you no longer need the database.)\n   * Errors such as bad permissions or a full disk may cause this method\n   * to fail, but future attempts may succeed if the problem is fixed.</p>\n   *\n   * <p class=\"caution\">Database upgrade may take a long time, you\n   * should not call this method from the application main thread, including\n   * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.\n   *\n   * @throws android.database.sqlite.SQLiteException if the database cannot be opened for writing\n   * @return a read/write database object valid until {@link #close} is called\n   */\n  @NonNull @CheckResult @WorkerThread\n  public SupportSQLiteDatabase getWritableDatabase() {\n    return helper.getWritableDatabase();\n  }\n\n  void sendTableTrigger(Set<String> tables) {\n    SqliteTransaction transaction = transactions.get();\n    if (transaction != null) {\n      transaction.addAll(tables);\n    } else {\n      if (logging) log(\"TRIGGER %s\", tables);\n      triggers.onNext(tables);\n    }\n  }\n\n  /**\n   * Begin a transaction for this thread.\n   * <p>\n   * Transactions may nest. If the transaction is not in progress, then a database connection is\n   * obtained and a new transaction is started. Otherwise, a nested transaction is started.\n   * <p>\n   * Each call to {@code newTransaction} must be matched exactly by a call to\n   * {@link Transaction#end()}. To mark a transaction as successful, call\n   * {@link Transaction#markSuccessful()} before calling {@link Transaction#end()}. If the\n   * transaction is not successful, or if any of its nested transactions were not successful, then\n   * the entire transaction will be rolled back when the outermost transaction is ended.\n   * <p>\n   * Transactions queue up all query notifications until they have been applied.\n   * <p>\n   * Here is the standard idiom for transactions:\n   *\n   * <pre>{@code\n   * try (Transaction transaction = db.newTransaction()) {\n   *   ...\n   *   transaction.markSuccessful();\n   * }\n   * }</pre>\n   *\n   * Manually call {@link Transaction#end()} when try-with-resources is not available:\n   * <pre>{@code\n   * Transaction transaction = db.newTransaction();\n   * try {\n   *   ...\n   *   transaction.markSuccessful();\n   * } finally {\n   *   transaction.end();\n   * }\n   * }</pre>\n   *\n   *\n   * @see SupportSQLiteDatabase#beginTransaction()\n   */\n  @CheckResult @NonNull\n  public Transaction newTransaction() {\n    SqliteTransaction transaction = new SqliteTransaction(transactions.get());\n    transactions.set(transaction);\n    if (logging) log(\"TXN BEGIN %s\", transaction);\n    getWritableDatabase().beginTransactionWithListener(transaction);\n\n    return this.transaction;\n  }\n\n  /**\n   * Begins a transaction in IMMEDIATE mode for this thread.\n   * <p>\n   * Transactions may nest. If the transaction is not in progress, then a database connection is\n   * obtained and a new transaction is started. Otherwise, a nested transaction is started.\n   * <p>\n   * Each call to {@code newNonExclusiveTransaction} must be matched exactly by a call to\n   * {@link Transaction#end()}. To mark a transaction as successful, call\n   * {@link Transaction#markSuccessful()} before calling {@link Transaction#end()}. If the\n   * transaction is not successful, or if any of its nested transactions were not successful, then\n   * the entire transaction will be rolled back when the outermost transaction is ended.\n   * <p>\n   * Transactions queue up all query notifications until they have been applied.\n   * <p>\n   * Here is the standard idiom for transactions:\n   *\n   * <pre>{@code\n   * try (Transaction transaction = db.newNonExclusiveTransaction()) {\n   *   ...\n   *   transaction.markSuccessful();\n   * }\n   * }</pre>\n   *\n   * Manually call {@link Transaction#end()} when try-with-resources is not available:\n   * <pre>{@code\n   * Transaction transaction = db.newNonExclusiveTransaction();\n   * try {\n   *   ...\n   *   transaction.markSuccessful();\n   * } finally {\n   *   transaction.end();\n   * }\n   * }</pre>\n   *\n   *\n   * @see SupportSQLiteDatabase#beginTransactionNonExclusive()\n   */\n  @CheckResult @NonNull\n  public Transaction newNonExclusiveTransaction() {\n    SqliteTransaction transaction = new SqliteTransaction(transactions.get());\n    transactions.set(transaction);\n    if (logging) log(\"TXN BEGIN %s\", transaction);\n    getWritableDatabase().beginTransactionWithListenerNonExclusive(transaction);\n\n    return this.transaction;\n  }\n\n  /**\n   * Close the underlying {@link SupportSQLiteOpenHelper} and remove cached readable and writeable\n   * databases. This does not prevent existing observables from retaining existing references as\n   * well as attempting to create new ones for new subscriptions.\n   */\n  @Override public void close() {\n    helper.close();\n  }\n\n  /**\n   * Create an observable which will notify subscribers with a {@linkplain Query query} for\n   * execution. Subscribers are responsible for <b>always</b> closing {@link Cursor} instance\n   * returned from the {@link Query}.\n   * <p>\n   * Subscribers will receive an immediate notification for initial data as well as subsequent\n   * notifications for when the supplied {@code table}'s data changes through the {@code insert},\n   * {@code update}, and {@code delete} methods of this class. Unsubscribe when you no longer want\n   * updates to a query.\n   * <p>\n   * Since database triggers are inherently asynchronous, items emitted from the returned\n   * observable use the {@link Scheduler} supplied to {@link SqlBrite#wrapDatabaseHelper}. For\n   * consistency, the immediate notification sent on subscribe also uses this scheduler. As such,\n   * calling {@link Observable#subscribeOn subscribeOn} on the returned observable has no effect.\n   * <p>\n   * Note: To skip the immediate notification and only receive subsequent notifications when data\n   * has changed call {@code skip(1)} on the returned observable.\n   * <p>\n   * <b>Warning:</b> this method does not perform the query! Only by subscribing to the returned\n   * {@link Observable} will the operation occur.\n   *\n   * @see SupportSQLiteDatabase#query(String, Object[])\n   */\n  @CheckResult @NonNull\n  public QueryObservable createQuery(@NonNull final String table, @NonNull String sql,\n      @NonNull Object... args) {\n    return createQuery(new DatabaseQuery(singletonList(table), new SimpleSQLiteQuery(sql, args)));\n  }\n\n  /**\n   * See {@link #createQuery(String, String, Object...)} for usage. This overload allows for\n   * monitoring multiple tables for changes.\n   *\n   * @see SupportSQLiteDatabase#query(String, Object[])\n   */\n  @CheckResult @NonNull\n  public QueryObservable createQuery(@NonNull final Iterable<String> tables, @NonNull String sql,\n      @NonNull Object... args) {\n    return createQuery(new DatabaseQuery(tables, new SimpleSQLiteQuery(sql, args)));\n  }\n\n  /**\n   * Create an observable which will notify subscribers with a {@linkplain Query query} for\n   * execution. Subscribers are responsible for <b>always</b> closing {@link Cursor} instance\n   * returned from the {@link Query}.\n   * <p>\n   * Subscribers will receive an immediate notification for initial data as well as subsequent\n   * notifications for when the supplied {@code table}'s data changes through the {@code insert},\n   * {@code update}, and {@code delete} methods of this class. Unsubscribe when you no longer want\n   * updates to a query.\n   * <p>\n   * Since database triggers are inherently asynchronous, items emitted from the returned\n   * observable use the {@link Scheduler} supplied to {@link SqlBrite#wrapDatabaseHelper}. For\n   * consistency, the immediate notification sent on subscribe also uses this scheduler. As such,\n   * calling {@link Observable#subscribeOn subscribeOn} on the returned observable has no effect.\n   * <p>\n   * Note: To skip the immediate notification and only receive subsequent notifications when data\n   * has changed call {@code skip(1)} on the returned observable.\n   * <p>\n   * <b>Warning:</b> this method does not perform the query! Only by subscribing to the returned\n   * {@link Observable} will the operation occur.\n   *\n   * @see SupportSQLiteDatabase#query(SupportSQLiteQuery)\n   */\n  @CheckResult @NonNull\n  public QueryObservable createQuery(@NonNull final String table,\n      @NonNull SupportSQLiteQuery query) {\n    return createQuery(new DatabaseQuery(singletonList(table), query));\n  }\n\n  /**\n   * See {@link #createQuery(String, SupportSQLiteQuery)} for usage. This overload allows for\n   * monitoring multiple tables for changes.\n   *\n   * @see SupportSQLiteDatabase#query(SupportSQLiteQuery)\n   */\n  @CheckResult @NonNull\n  public QueryObservable createQuery(@NonNull final Iterable<String> tables,\n      @NonNull SupportSQLiteQuery query) {\n    return createQuery(new DatabaseQuery(tables, query));\n  }\n\n  @CheckResult @NonNull\n  private QueryObservable createQuery(DatabaseQuery query) {\n    if (transactions.get() != null) {\n      throw new IllegalStateException(\"Cannot create observable query in transaction. \"\n          + \"Use query() for a query inside a transaction.\");\n    }\n\n    return triggers //\n        .filter(query) // DatabaseQuery filters triggers to on tables we care about.\n        .map(query) // DatabaseQuery maps to itself to save an allocation.\n        .startWith(query) //\n        .observeOn(scheduler) //\n        .compose(queryTransformer) // Apply the user's query transformer.\n        .doOnSubscribe(ensureNotInTransaction)\n        .to(QUERY_OBSERVABLE);\n  }\n\n  /**\n   * Runs the provided SQL and returns a {@link Cursor} over the result set.\n   *\n   * @see SupportSQLiteDatabase#query(String, Object[])\n   */\n  @CheckResult @WorkerThread\n  public Cursor query(@NonNull String sql, @NonNull Object... args) {\n    Cursor cursor = getReadableDatabase().query(sql, args);\n    if (logging) {\n      log(\"QUERY\\n  sql: %s\\n  args: %s\", indentSql(sql), Arrays.toString(args));\n    }\n\n    return cursor;\n  }\n\n  /**\n   * Runs the provided {@link SupportSQLiteQuery} and returns a {@link Cursor} over the result set.\n   *\n   * @see SupportSQLiteDatabase#query(SupportSQLiteQuery)\n   */\n  @CheckResult @WorkerThread\n  public Cursor query(@NonNull SupportSQLiteQuery query) {\n    Cursor cursor = getReadableDatabase().query(query);\n    if (logging) {\n      log(\"QUERY\\n  sql: %s\", indentSql(query.getSql()));\n    }\n\n    return cursor;\n  }\n\n  /**\n   * Insert a row into the specified {@code table} and notify any subscribed queries.\n   *\n   * @see SupportSQLiteDatabase#insert(String, int, ContentValues)\n   */\n  @WorkerThread\n  public long insert(@NonNull String table, @ConflictAlgorithm int conflictAlgorithm,\n      @NonNull ContentValues values) {\n    SupportSQLiteDatabase db = getWritableDatabase();\n\n    if (logging) {\n      log(\"INSERT\\n  table: %s\\n  values: %s\\n  conflictAlgorithm: %s\", table, values,\n          conflictString(conflictAlgorithm));\n    }\n    long rowId = db.insert(table, conflictAlgorithm, values);\n\n    if (logging) log(\"INSERT id: %s\", rowId);\n\n    if (rowId != -1) {\n      // Only send a table trigger if the insert was successful.\n      sendTableTrigger(Collections.singleton(table));\n    }\n    return rowId;\n  }\n\n  /**\n   * Delete rows from the specified {@code table} and notify any subscribed queries. This method\n   * will not trigger a notification if no rows were deleted.\n   *\n   * @see SupportSQLiteDatabase#delete(String, String, Object[])\n   */\n  @WorkerThread\n  public int delete(@NonNull String table, @Nullable String whereClause,\n      @Nullable String... whereArgs) {\n    SupportSQLiteDatabase db = getWritableDatabase();\n\n    if (logging) {\n      log(\"DELETE\\n  table: %s\\n  whereClause: %s\\n  whereArgs: %s\", table, whereClause,\n          Arrays.toString(whereArgs));\n    }\n    int rows = db.delete(table, whereClause, whereArgs);\n\n    if (logging) log(\"DELETE affected %s %s\", rows, rows != 1 ? \"rows\" : \"row\");\n\n    if (rows > 0) {\n      // Only send a table trigger if rows were affected.\n      sendTableTrigger(Collections.singleton(table));\n    }\n    return rows;\n  }\n\n  /**\n   * Update rows in the specified {@code table} and notify any subscribed queries. This method\n   * will not trigger a notification if no rows were updated.\n   *\n   * @see SupportSQLiteDatabase#update(String, int, ContentValues, String, Object[])\n   */\n  @WorkerThread\n  public int update(@NonNull String table, @ConflictAlgorithm int conflictAlgorithm,\n      @NonNull ContentValues values, @Nullable String whereClause, @Nullable String... whereArgs) {\n    SupportSQLiteDatabase db = getWritableDatabase();\n\n    if (logging) {\n      log(\"UPDATE\\n  table: %s\\n  values: %s\\n  whereClause: %s\\n  whereArgs: %s\\n  conflictAlgorithm: %s\",\n          table, values, whereClause, Arrays.toString(whereArgs),\n          conflictString(conflictAlgorithm));\n    }\n    int rows = db.update(table, conflictAlgorithm, values, whereClause, whereArgs);\n\n    if (logging) log(\"UPDATE affected %s %s\", rows, rows != 1 ? \"rows\" : \"row\");\n\n    if (rows > 0) {\n      // Only send a table trigger if rows were affected.\n      sendTableTrigger(Collections.singleton(table));\n    }\n    return rows;\n  }\n\n  /**\n   * Execute {@code sql} provided it is NOT a {@code SELECT} or any other SQL statement that\n   * returns data. No data can be returned (such as the number of affected rows). Instead, use\n   * {@link #insert}, {@link #update}, et al, when possible.\n   * <p>\n   * No notifications will be sent to queries if {@code sql} affects the data of a table.\n   *\n   * @see SupportSQLiteDatabase#execSQL(String)\n   */\n  @WorkerThread\n  public void execute(String sql) {\n    if (logging) log(\"EXECUTE\\n  sql: %s\", indentSql(sql));\n\n    getWritableDatabase().execSQL(sql);\n  }\n\n  /**\n   * Execute {@code sql} provided it is NOT a {@code SELECT} or any other SQL statement that\n   * returns data. No data can be returned (such as the number of affected rows). Instead, use\n   * {@link #insert}, {@link #update}, et al, when possible.\n   * <p>\n   * No notifications will be sent to queries if {@code sql} affects the data of a table.\n   *\n   * @see SupportSQLiteDatabase#execSQL(String, Object[])\n   */\n  @WorkerThread\n  public void execute(String sql, Object... args) {\n    if (logging) log(\"EXECUTE\\n  sql: %s\\n  args: %s\", indentSql(sql), Arrays.toString(args));\n\n    getWritableDatabase().execSQL(sql, args);\n  }\n\n  /**\n   * Execute {@code sql} provided it is NOT a {@code SELECT} or any other SQL statement that\n   * returns data. No data can be returned (such as the number of affected rows). Instead, use\n   * {@link #insert}, {@link #update}, et al, when possible.\n   * <p>\n   * A notification to queries for {@code table} will be sent after the statement is executed.\n   *\n   * @see SupportSQLiteDatabase#execSQL(String)\n   */\n  @WorkerThread\n  public void executeAndTrigger(String table, String sql) {\n    executeAndTrigger(Collections.singleton(table), sql);\n  }\n\n  /**\n   * See {@link #executeAndTrigger(String, String)} for usage. This overload allows for triggering multiple tables.\n   *\n   * @see BriteDatabase#executeAndTrigger(String, String)\n   */\n  @WorkerThread\n  public void executeAndTrigger(Set<String> tables, String sql) {\n    execute(sql);\n\n    sendTableTrigger(tables);\n  }\n\n  /**\n   * Execute {@code sql} provided it is NOT a {@code SELECT} or any other SQL statement that\n   * returns data. No data can be returned (such as the number of affected rows). Instead, use\n   * {@link #insert}, {@link #update}, et al, when possible.\n   * <p>\n   * A notification to queries for {@code table} will be sent after the statement is executed.\n   *\n   * @see SupportSQLiteDatabase#execSQL(String, Object[])\n   */\n  @WorkerThread\n  public void executeAndTrigger(String table, String sql, Object... args) {\n    executeAndTrigger(Collections.singleton(table), sql, args);\n  }\n\n  /**\n   * See {@link #executeAndTrigger(String, String, Object...)} for usage. This overload allows for triggering multiple tables.\n   *\n   * @see BriteDatabase#executeAndTrigger(String, String, Object...)\n   */\n  @WorkerThread\n  public void executeAndTrigger(Set<String> tables, String sql, Object... args) {\n    execute(sql, args);\n\n    sendTableTrigger(tables);\n  }\n\n  /**\n   * Execute {@code statement}, if the the number of rows affected by execution of this SQL\n   * statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.\n   *\n   * @return the number of rows affected by this SQL statement execution.\n   * @throws android.database.SQLException If the SQL string is invalid\n   *\n   * @see SupportSQLiteStatement#executeUpdateDelete()\n   */\n  @WorkerThread\n  public int executeUpdateDelete(String table, SupportSQLiteStatement statement) {\n    return executeUpdateDelete(Collections.singleton(table), statement);\n  }\n\n  /**\n   * See {@link #executeUpdateDelete(String, SupportSQLiteStatement)} for usage. This overload\n   * allows for triggering multiple tables.\n   *\n   * @see BriteDatabase#executeUpdateDelete(String, SupportSQLiteStatement)\n   */\n  @WorkerThread\n  public int executeUpdateDelete(Set<String> tables, SupportSQLiteStatement statement) {\n    if (logging) log(\"EXECUTE\\n %s\", statement);\n\n    int rows = statement.executeUpdateDelete();\n    if (rows > 0) {\n      // Only send a table trigger if rows were affected.\n      sendTableTrigger(tables);\n    }\n    return rows;\n  }\n\n  /**\n   * Execute {@code statement} and return the ID of the row inserted due to this call.\n   * The SQL statement should be an INSERT for this to be a useful call.\n   *\n   * @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.\n   *\n   * @throws android.database.SQLException If the SQL string is invalid\n   *\n   * @see SupportSQLiteStatement#executeInsert()\n   */\n  @WorkerThread\n  public long executeInsert(String table, SupportSQLiteStatement statement) {\n    return executeInsert(Collections.singleton(table), statement);\n  }\n\n  /**\n   * See {@link #executeInsert(String, SupportSQLiteStatement)} for usage. This overload allows for\n   * triggering multiple tables.\n   *\n   * @see BriteDatabase#executeInsert(String, SupportSQLiteStatement)\n   */\n  @WorkerThread\n  public long executeInsert(Set<String> tables, SupportSQLiteStatement statement) {\n    if (logging) log(\"EXECUTE\\n %s\", statement);\n\n    long rowId = statement.executeInsert();\n    if (rowId != -1) {\n      // Only send a table trigger if the insert was successful.\n      sendTableTrigger(tables);\n    }\n    return rowId;\n  }\n\n  /** An in-progress database transaction. */\n  public interface Transaction extends Closeable {\n    /**\n     * End a transaction. See {@link #newTransaction()} for notes about how to use this and when\n     * transactions are committed and rolled back.\n     *\n     * @see SupportSQLiteDatabase#endTransaction()\n     */\n    @WorkerThread\n    void end();\n\n    /**\n     * Marks the current transaction as successful. Do not do any more database work between\n     * calling this and calling {@link #end()}. Do as little non-database work as possible in that\n     * situation too. If any errors are encountered between this and {@link #end()} the transaction\n     * will still be committed.\n     *\n     * @see SupportSQLiteDatabase#setTransactionSuccessful()\n     */\n    @WorkerThread\n    void markSuccessful();\n\n    /**\n     * Temporarily end the transaction to let other threads run. The transaction is assumed to be\n     * successful so far. Do not call {@link #markSuccessful()} before calling this. When this\n     * returns a new transaction will have been created but not marked as successful. This assumes\n     * that there are no nested transactions (newTransaction has only been called once) and will\n     * throw an exception if that is not the case.\n     *\n     * @return true if the transaction was yielded\n     *\n     * @see SupportSQLiteDatabase#yieldIfContendedSafely()\n     */\n    @WorkerThread\n    boolean yieldIfContendedSafely();\n\n    /**\n     * Temporarily end the transaction to let other threads run. The transaction is assumed to be\n     * successful so far. Do not call {@link #markSuccessful()} before calling this. When this\n     * returns a new transaction will have been created but not marked as successful. This assumes\n     * that there are no nested transactions (newTransaction has only been called once) and will\n     * throw an exception if that is not the case.\n     *\n     * @param sleepAmount if > 0, sleep this long before starting a new transaction if\n     *   the lock was actually yielded. This will allow other background threads to make some\n     *   more progress than they would if we started the transaction immediately.\n     * @return true if the transaction was yielded\n     *\n     * @see SupportSQLiteDatabase#yieldIfContendedSafely(long)\n     */\n    @WorkerThread\n    boolean yieldIfContendedSafely(long sleepAmount, TimeUnit sleepUnit);\n\n    /**\n     * Equivalent to calling {@link #end()}\n     */\n    @WorkerThread\n    @Override void close();\n  }\n\n  @IntDef({\n      CONFLICT_ABORT,\n      CONFLICT_FAIL,\n      CONFLICT_IGNORE,\n      CONFLICT_NONE,\n      CONFLICT_REPLACE,\n      CONFLICT_ROLLBACK\n  })\n  @Retention(SOURCE)\n  private @interface ConflictAlgorithm {\n  }\n\n  static String indentSql(String sql) {\n    return sql.replace(\"\\n\", \"\\n       \");\n  }\n\n  void log(String message, Object... args) {\n    if (args.length > 0) message = String.format(message, args);\n    logger.log(message);\n  }\n\n  private static String conflictString(@ConflictAlgorithm int conflictAlgorithm) {\n    switch (conflictAlgorithm) {\n      case CONFLICT_ABORT:\n        return \"abort\";\n      case CONFLICT_FAIL:\n        return \"fail\";\n      case CONFLICT_IGNORE:\n        return \"ignore\";\n      case CONFLICT_NONE:\n        return \"none\";\n      case CONFLICT_REPLACE:\n        return \"replace\";\n      case CONFLICT_ROLLBACK:\n        return \"rollback\";\n      default:\n        return \"unknown (\" + conflictAlgorithm + ')';\n    }\n  }\n\n  static final class SqliteTransaction extends LinkedHashSet<String>\n      implements SQLiteTransactionListener {\n    final SqliteTransaction parent;\n    boolean commit;\n\n    SqliteTransaction(SqliteTransaction parent) {\n      this.parent = parent;\n    }\n\n    @Override public void onBegin() {\n    }\n\n    @Override public void onCommit() {\n      commit = true;\n    }\n\n    @Override public void onRollback() {\n    }\n\n    @Override public String toString() {\n      String name = String.format(\"%08x\", System.identityHashCode(this));\n      return parent == null ? name : name + \" [\" + parent.toString() + ']';\n    }\n  }\n\n  final class DatabaseQuery extends Query\n      implements Function<Set<String>, Query>, Predicate<Set<String>> {\n    private final Iterable<String> tables;\n    private final SupportSQLiteQuery query;\n\n    DatabaseQuery(Iterable<String> tables, SupportSQLiteQuery query) {\n      this.tables = tables;\n      this.query = query;\n    }\n\n    @Override public Cursor run() {\n      if (transactions.get() != null) {\n        throw new IllegalStateException(\"Cannot execute observable query in a transaction.\");\n      }\n\n      Cursor cursor = getReadableDatabase().query(query);\n\n      if (logging) {\n        log(\"QUERY\\n  tables: %s\\n  sql: %s\", tables, indentSql(query.getSql()));\n      }\n\n      return cursor;\n    }\n\n    @Override public String toString() {\n      return query.getSql();\n    }\n\n    @Override public Query apply(Set<String> ignored) {\n      return this;\n    }\n\n    @Override public boolean test(Set<String> strings) {\n      for (String table : tables) {\n        if (strings.contains(table)) {\n          return true;\n        }\n      }\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/QueryObservable.java",
    "content": "package com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.os.Build;\nimport android.support.annotation.CheckResult;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.RequiresApi;\nimport com.squareup.sqlbrite3.SqlBrite.Query;\nimport io.reactivex.Observable;\nimport io.reactivex.Observer;\nimport io.reactivex.functions.Function;\nimport java.util.List;\nimport java.util.Optional;\n\n/** An {@link Observable} of {@link Query} which offers query-specific convenience operators. */\npublic final class QueryObservable extends Observable<Query> {\n  static final Function<Observable<Query>, QueryObservable> QUERY_OBSERVABLE =\n      new Function<Observable<Query>, QueryObservable>() {\n        @Override public QueryObservable apply(Observable<Query> queryObservable) {\n          return new QueryObservable(queryObservable);\n        }\n      };\n\n  private final Observable<Query> upstream;\n\n  public QueryObservable(Observable<Query> upstream) {\n    this.upstream = upstream;\n  }\n\n  @Override protected void subscribeActual(Observer<? super Query> observer) {\n    upstream.subscribe(observer);\n  }\n\n  /**\n   * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each\n   * emitted {@link Query} which returns a single row to {@code T}.\n   * <p>\n   * It is an error for a query to pass through this operator with more than 1 row in its result\n   * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n   * do not emit an item.\n   * <p>\n   * This method is equivalent to:\n   * <pre>{@code\n   * flatMap(q -> q.asRows(mapper).take(1))\n   * }</pre>\n   * and a convenience operator for:\n   * <pre>{@code\n   * lift(Query.mapToOne(mapper))\n   * }</pre>\n   *\n   * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n   */\n  @CheckResult @NonNull\n  public final <T> Observable<T> mapToOne(@NonNull Function<Cursor, T> mapper) {\n    return lift(Query.mapToOne(mapper));\n  }\n\n  /**\n   * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each\n   * emitted {@link Query} which returns a single row to {@code T}.\n   * <p>\n   * It is an error for a query to pass through this operator with more than 1 row in its result\n   * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n   * emit {@code defaultValue}.\n   * <p>\n   * This method is equivalent to:\n   * <pre>{@code\n   * flatMap(q -> q.asRows(mapper).take(1).defaultIfEmpty(defaultValue))\n   * }</pre>\n   * and a convenience operator for:\n   * <pre>{@code\n   * lift(Query.mapToOneOrDefault(mapper, defaultValue))\n   * }</pre>\n   *\n   * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n   * @param defaultValue Value returned if result set is empty\n   */\n  @CheckResult @NonNull\n  public final <T> Observable<T> mapToOneOrDefault(@NonNull Function<Cursor, T> mapper,\n      @NonNull T defaultValue) {\n    return lift(Query.mapToOneOrDefault(mapper, defaultValue));\n  }\n\n  /**\n   * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each\n   * emitted {@link Query} which returns a single row to {@code Optional<T>}.\n   * <p>\n   * It is an error for a query to pass through this operator with more than 1 row in its result\n   * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n   * emit {@link Optional#empty() Optional.empty()}\n   * <p>\n   * This method is equivalent to:\n   * <pre>{@code\n   * flatMap(q -> q.asRows(mapper).take(1).map(Optional::of).defaultIfEmpty(Optional.empty())\n   * }</pre>\n   * and a convenience operator for:\n   * <pre>{@code\n   * lift(Query.mapToOptional(mapper))\n   * }</pre>\n   *\n   * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n   */\n  @RequiresApi(Build.VERSION_CODES.N)\n  @CheckResult @NonNull\n  public final <T> Observable<Optional<T>> mapToOptional(@NonNull Function<Cursor, T> mapper) {\n    return lift(Query.mapToOptional(mapper));\n  }\n\n  /**\n   * Given a function mapping the current row of a {@link Cursor} to {@code T}, transform each\n   * emitted {@link Query} to a {@code List<T>}.\n   * <p>\n   * Be careful using this operator as it will always consume the entire cursor and create objects\n   * for each row, every time this observable emits a new query. On tables whose queries update\n   * frequently or very large result sets this can result in the creation of many objects.\n   * <p>\n   * This method is equivalent to:\n   * <pre>{@code\n   * flatMap(q -> q.asRows(mapper).toList())\n   * }</pre>\n   * and a convenience operator for:\n   * <pre>{@code\n   * lift(Query.mapToList(mapper))\n   * }</pre>\n   * <p>\n   * Consider using {@link Query#asRows} if you need to limit or filter in memory.\n   *\n   * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n   */\n  @CheckResult @NonNull\n  public final <T> Observable<List<T>> mapToList(@NonNull Function<Cursor, T> mapper) {\n    return lift(Query.mapToList(mapper));\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/QueryToListOperator.java",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport io.reactivex.ObservableOperator;\nimport io.reactivex.Observer;\nimport io.reactivex.exceptions.Exceptions;\nimport io.reactivex.functions.Function;\nimport io.reactivex.observers.DisposableObserver;\nimport io.reactivex.plugins.RxJavaPlugins;\nimport java.util.ArrayList;\nimport java.util.List;\n\nfinal class QueryToListOperator<T> implements ObservableOperator<List<T>, SqlBrite.Query> {\n  private final Function<Cursor, T> mapper;\n\n  QueryToListOperator(Function<Cursor, T> mapper) {\n    this.mapper = mapper;\n  }\n\n  @Override public Observer<? super SqlBrite.Query> apply(Observer<? super List<T>> observer) {\n    return new MappingObserver<>(observer, mapper);\n  }\n\n  static final class MappingObserver<T> extends DisposableObserver<SqlBrite.Query> {\n    private final Observer<? super List<T>> downstream;\n    private final Function<Cursor, T> mapper;\n\n    MappingObserver(Observer<? super List<T>> downstream, Function<Cursor, T> mapper) {\n      this.downstream = downstream;\n      this.mapper = mapper;\n    }\n\n    @Override protected void onStart() {\n      downstream.onSubscribe(this);\n    }\n\n    @Override public void onNext(SqlBrite.Query query) {\n      try {\n        Cursor cursor = query.run();\n        if (cursor == null || isDisposed()) {\n          return;\n        }\n        List<T> items = new ArrayList<>(cursor.getCount());\n        try {\n          while (cursor.moveToNext()) {\n            items.add(mapper.apply(cursor));\n          }\n        } finally {\n          cursor.close();\n        }\n        if (!isDisposed()) {\n          downstream.onNext(items);\n        }\n      } catch (Throwable e) {\n        Exceptions.throwIfFatal(e);\n        onError(e);\n      }\n    }\n\n    @Override public void onComplete() {\n      if (!isDisposed()) {\n        downstream.onComplete();\n      }\n    }\n\n    @Override public void onError(Throwable e) {\n      if (isDisposed()) {\n        RxJavaPlugins.onError(e);\n      } else {\n        downstream.onError(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/QueryToOneOperator.java",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.support.annotation.Nullable;\nimport io.reactivex.ObservableOperator;\nimport io.reactivex.Observer;\nimport io.reactivex.exceptions.Exceptions;\nimport io.reactivex.functions.Function;\nimport io.reactivex.observers.DisposableObserver;\nimport io.reactivex.plugins.RxJavaPlugins;\n\nfinal class QueryToOneOperator<T> implements ObservableOperator<T, SqlBrite.Query> {\n  private final Function<Cursor, T> mapper;\n  private final T defaultValue;\n\n  /** A null {@code defaultValue} means nothing will be emitted when empty. */\n  QueryToOneOperator(Function<Cursor, T> mapper, @Nullable T defaultValue) {\n    this.mapper = mapper;\n    this.defaultValue = defaultValue;\n  }\n\n  @Override public Observer<? super SqlBrite.Query> apply(Observer<? super T> observer) {\n    return new MappingObserver<>(observer, mapper, defaultValue);\n  }\n\n  static final class MappingObserver<T> extends DisposableObserver<SqlBrite.Query> {\n    private final Observer<? super T> downstream;\n    private final Function<Cursor, T> mapper;\n    private final T defaultValue;\n\n    MappingObserver(Observer<? super T> downstream, Function<Cursor, T> mapper, T defaultValue) {\n      this.downstream = downstream;\n      this.mapper = mapper;\n      this.defaultValue = defaultValue;\n    }\n\n    @Override protected void onStart() {\n      downstream.onSubscribe(this);\n    }\n\n    @Override public void onNext(SqlBrite.Query query) {\n      try {\n        T item = null;\n        Cursor cursor = query.run();\n        if (cursor != null) {\n          try {\n            if (cursor.moveToNext()) {\n              item = mapper.apply(cursor);\n              if (item == null) {\n                downstream.onError(new NullPointerException(\"QueryToOne mapper returned null\"));\n                return;\n              }\n              if (cursor.moveToNext()) {\n                throw new IllegalStateException(\"Cursor returned more than 1 row\");\n              }\n            }\n          } finally {\n            cursor.close();\n          }\n        }\n        if (!isDisposed()) {\n          if (item != null) {\n            downstream.onNext(item);\n          } else if (defaultValue != null) {\n            downstream.onNext(defaultValue);\n          }\n        }\n      } catch (Throwable e) {\n        Exceptions.throwIfFatal(e);\n        onError(e);\n      }\n    }\n\n    @Override public void onComplete() {\n      if (!isDisposed()) {\n        downstream.onComplete();\n      }\n    }\n\n    @Override public void onError(Throwable e) {\n      if (isDisposed()) {\n        RxJavaPlugins.onError(e);\n      } else {\n        downstream.onError(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/QueryToOptionalOperator.java",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.database.Cursor;\nimport android.os.Build;\nimport android.support.annotation.RequiresApi;\nimport io.reactivex.ObservableOperator;\nimport io.reactivex.Observer;\nimport io.reactivex.exceptions.Exceptions;\nimport io.reactivex.functions.Function;\nimport io.reactivex.observers.DisposableObserver;\nimport io.reactivex.plugins.RxJavaPlugins;\nimport java.util.Optional;\n\n@RequiresApi(Build.VERSION_CODES.N)\nfinal class QueryToOptionalOperator<T> implements ObservableOperator<Optional<T>, SqlBrite.Query> {\n  private final Function<Cursor, T> mapper;\n\n  QueryToOptionalOperator(Function<Cursor, T> mapper) {\n    this.mapper = mapper;\n  }\n\n  @Override public Observer<? super SqlBrite.Query> apply(Observer<? super Optional<T>> observer) {\n    return new MappingObserver<>(observer, mapper);\n  }\n\n  static final class MappingObserver<T> extends DisposableObserver<SqlBrite.Query> {\n    private final Observer<? super Optional<T>> downstream;\n    private final Function<Cursor, T> mapper;\n\n    MappingObserver(Observer<? super Optional<T>> downstream, Function<Cursor, T> mapper) {\n      this.downstream = downstream;\n      this.mapper = mapper;\n    }\n\n    @Override protected void onStart() {\n      downstream.onSubscribe(this);\n    }\n\n    @Override public void onNext(SqlBrite.Query query) {\n      try {\n        T item = null;\n        Cursor cursor = query.run();\n        if (cursor != null) {\n          try {\n            if (cursor.moveToNext()) {\n              item = mapper.apply(cursor);\n              if (item == null) {\n                downstream.onError(new NullPointerException(\"QueryToOne mapper returned null\"));\n                return;\n              }\n              if (cursor.moveToNext()) {\n                throw new IllegalStateException(\"Cursor returned more than 1 row\");\n              }\n            }\n          } finally {\n            cursor.close();\n          }\n        }\n        if (!isDisposed()) {\n          downstream.onNext(Optional.ofNullable(item));\n        }\n      } catch (Throwable e) {\n        Exceptions.throwIfFatal(e);\n        onError(e);\n      }\n    }\n\n    @Override public void onComplete() {\n      if (!isDisposed()) {\n        downstream.onComplete();\n      }\n    }\n\n    @Override public void onError(Throwable e) {\n      if (isDisposed()) {\n        RxJavaPlugins.onError(e);\n      } else {\n        downstream.onError(e);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "sqlbrite/src/main/java/com/squareup/sqlbrite3/SqlBrite.java",
    "content": "/*\n * Copyright (C) 2015 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3;\n\nimport android.arch.persistence.db.SupportSQLiteOpenHelper;\nimport android.content.ContentResolver;\nimport android.database.Cursor;\nimport android.os.Build;\nimport android.support.annotation.CheckResult;\nimport android.support.annotation.NonNull;\nimport android.support.annotation.Nullable;\nimport android.support.annotation.RequiresApi;\nimport android.support.annotation.WorkerThread;\nimport android.util.Log;\nimport io.reactivex.Observable;\nimport io.reactivex.ObservableEmitter;\nimport io.reactivex.ObservableOnSubscribe;\nimport io.reactivex.ObservableOperator;\nimport io.reactivex.ObservableTransformer;\nimport io.reactivex.Scheduler;\nimport io.reactivex.functions.Function;\nimport java.util.List;\nimport java.util.Optional;\n\n/**\n * A lightweight wrapper around {@link SupportSQLiteOpenHelper} which allows for continuously\n * observing the result of a query.\n */\npublic final class SqlBrite {\n  static final Logger DEFAULT_LOGGER = new Logger() {\n    @Override public void log(String message) {\n      Log.d(\"SqlBrite\", message);\n    }\n  };\n  static final ObservableTransformer<Query, Query> DEFAULT_TRANSFORMER =\n      new ObservableTransformer<Query, Query>() {\n        @Override public Observable<Query> apply(Observable<Query> queryObservable) {\n          return queryObservable;\n        }\n      };\n\n  public static final class Builder {\n    private Logger logger = DEFAULT_LOGGER;\n    private ObservableTransformer<Query, Query> queryTransformer = DEFAULT_TRANSFORMER;\n\n    @CheckResult\n    public Builder logger(@NonNull Logger logger) {\n      if (logger == null) throw new NullPointerException(\"logger == null\");\n      this.logger = logger;\n      return this;\n    }\n\n    @CheckResult\n    public Builder queryTransformer(@NonNull ObservableTransformer<Query, Query> queryTransformer) {\n      if (queryTransformer == null) throw new NullPointerException(\"queryTransformer == null\");\n      this.queryTransformer = queryTransformer;\n      return this;\n    }\n\n    @CheckResult\n    public SqlBrite build() {\n      return new SqlBrite(logger, queryTransformer);\n    }\n  }\n\n  final Logger logger;\n  final ObservableTransformer<Query, Query> queryTransformer;\n\n  SqlBrite(@NonNull Logger logger, @NonNull ObservableTransformer<Query, Query> queryTransformer) {\n    this.logger = logger;\n    this.queryTransformer = queryTransformer;\n  }\n\n  /**\n   * Wrap a {@link SupportSQLiteOpenHelper} for observable queries.\n   * <p>\n   * While not strictly required, instances of this class assume that they will be the only ones\n   * interacting with the underlying {@link SupportSQLiteOpenHelper} and it is required for\n   * automatic notifications of table changes to work. See {@linkplain BriteDatabase#createQuery the\n   * <code>query</code> method} for more information on that behavior.\n   *\n   * @param scheduler The {@link Scheduler} on which items from {@link BriteDatabase#createQuery}\n   * will be emitted.\n   */\n  @CheckResult @NonNull public BriteDatabase wrapDatabaseHelper(\n      @NonNull SupportSQLiteOpenHelper helper,\n      @NonNull Scheduler scheduler) {\n    return new BriteDatabase(helper, logger, scheduler, queryTransformer);\n  }\n\n  /**\n   * Wrap a {@link ContentResolver} for observable queries.\n   *\n   * @param scheduler The {@link Scheduler} on which items from\n   * {@link BriteContentResolver#createQuery} will be emitted.\n   */\n  @CheckResult @NonNull public BriteContentResolver wrapContentProvider(\n      @NonNull ContentResolver contentResolver, @NonNull Scheduler scheduler) {\n    return new BriteContentResolver(contentResolver, logger, scheduler, queryTransformer);\n  }\n\n  /** An executable query. */\n  public static abstract class Query {\n    /**\n     * Creates an {@linkplain ObservableOperator operator} which transforms a query returning a\n     * single row to a {@code T} using {@code mapper}. Use with {@link Observable#lift}.\n     * <p>\n     * It is an error for a query to pass through this operator with more than 1 row in its result\n     * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n     * do not emit an item.\n     * <p>\n     * This operator ignores {@code null} cursors returned from {@link #run()}.\n     *\n     * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n     */\n    @CheckResult @NonNull //\n    public static <T> ObservableOperator<T, Query> mapToOne(@NonNull Function<Cursor, T> mapper) {\n      return new QueryToOneOperator<>(mapper, null);\n    }\n\n    /**\n     * Creates an {@linkplain ObservableOperator operator} which transforms a query returning a\n     * single row to a {@code T} using {@code mapper}. Use with {@link Observable#lift}.\n     * <p>\n     * It is an error for a query to pass through this operator with more than 1 row in its result\n     * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n     * emit {@code defaultValue}.\n     * <p>\n     * This operator emits {@code defaultValue} if {@code null} is returned from {@link #run()}.\n     *\n     * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n     * @param defaultValue Value returned if result set is empty\n     */\n    @SuppressWarnings(\"ConstantConditions\") // Public API contract.\n    @CheckResult @NonNull\n    public static <T> ObservableOperator<T, Query> mapToOneOrDefault(\n        @NonNull Function<Cursor, T> mapper, @NonNull T defaultValue) {\n      if (defaultValue == null) throw new NullPointerException(\"defaultValue == null\");\n      return new QueryToOneOperator<>(mapper, defaultValue);\n    }\n\n    /**\n     * Creates an {@linkplain ObservableOperator operator} which transforms a query returning a\n     * single row to a {@code Optional<T>} using {@code mapper}. Use with {@link Observable#lift}.\n     * <p>\n     * It is an error for a query to pass through this operator with more than 1 row in its result\n     * set. Use {@code LIMIT 1} on the underlying SQL query to prevent this. Result sets with 0 rows\n     * emit {@link Optional#empty() Optional.empty()}.\n     * <p>\n     * This operator ignores {@code null} cursors returned from {@link #run()}.\n     *\n     * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n     */\n    @RequiresApi(Build.VERSION_CODES.N) //\n    @CheckResult @NonNull //\n    public static <T> ObservableOperator<Optional<T>, Query> mapToOptional(\n        @NonNull Function<Cursor, T> mapper) {\n      return new QueryToOptionalOperator<>(mapper);\n    }\n\n    /**\n     * Creates an {@linkplain ObservableOperator operator} which transforms a query to a\n     * {@code List<T>} using {@code mapper}. Use with {@link Observable#lift}.\n     * <p>\n     * Be careful using this operator as it will always consume the entire cursor and create objects\n     * for each row, every time this observable emits a new query. On tables whose queries update\n     * frequently or very large result sets this can result in the creation of many objects.\n     * <p>\n     * This operator ignores {@code null} cursors returned from {@link #run()}.\n     *\n     * @param mapper Maps the current {@link Cursor} row to {@code T}. May not return null.\n     */\n    @CheckResult @NonNull\n    public static <T> ObservableOperator<List<T>, Query> mapToList(\n        @NonNull Function<Cursor, T> mapper) {\n      return new QueryToListOperator<>(mapper);\n    }\n\n    /**\n     * Execute the query on the underlying database and return the resulting cursor.\n     *\n     * @return A {@link Cursor} with query results, or {@code null} when the query could not be\n     * executed due to a problem with the underlying store. Unfortunately it is not well documented\n     * when {@code null} is returned. It usually involves a problem in communicating with the\n     * underlying store and should either be treated as failure or ignored for retry at a later\n     * time.\n     */\n    @CheckResult @WorkerThread\n    @Nullable\n    public abstract Cursor run();\n\n    /**\n     * Execute the query on the underlying database and return an Observable of each row mapped to\n     * {@code T} by {@code mapper}.\n     * <p>\n     * Standard usage of this operation is in {@code flatMap}:\n     * <pre>{@code\n     * flatMap(q -> q.asRows(Item.MAPPER).toList())\n     * }</pre>\n     * However, the above is a more-verbose but identical operation as\n     * {@link QueryObservable#mapToList}. This {@code asRows} method should be used when you need\n     * to limit or filter the items separate from the actual query.\n     * <pre>{@code\n     * flatMap(q -> q.asRows(Item.MAPPER).take(5).toList())\n     * // or...\n     * flatMap(q -> q.asRows(Item.MAPPER).filter(i -> i.isActive).toList())\n     * }</pre>\n     * <p>\n     * Note: Limiting results or filtering will almost always be faster in the database as part of\n     * a query and should be preferred, where possible.\n     * <p>\n     * The resulting observable will be empty if {@code null} is returned from {@link #run()}.\n     */\n    @CheckResult @NonNull\n    public final <T> Observable<T> asRows(final Function<Cursor, T> mapper) {\n      return Observable.create(new ObservableOnSubscribe<T>() {\n        @Override public void subscribe(ObservableEmitter<T> e) throws Exception {\n          Cursor cursor = run();\n          if (cursor != null) {\n            try {\n              while (cursor.moveToNext() && !e.isDisposed()) {\n                e.onNext(mapper.apply(cursor));\n              }\n            } finally {\n              cursor.close();\n            }\n          }\n          if (!e.isDisposed()) {\n            e.onComplete();\n          }\n        }\n      });\n    }\n  }\n\n  /** A simple indirection for logging debug messages. */\n  public interface Logger {\n    void log(String message);\n  }\n}\n"
  },
  {
    "path": "sqlbrite-kotlin/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'org.jetbrains.kotlin.android'\n\ndependencies {\n  api project(':sqlbrite')\n  api rootProject.ext.kotlinStdLib\n}\n\nandroid {\n  compileSdkVersion versions.compileSdk\n\n  defaultConfig {\n    minSdkVersion versions.minSdk\n  }\n\n  compileOptions {\n    sourceCompatibility JavaVersion.VERSION_1_7\n    targetCompatibility JavaVersion.VERSION_1_7\n  }\n\n  lintOptions {\n    textOutput 'stdout'\n    textReport true\n  }\n\n  // TODO replace with https://issuetracker.google.com/issues/72050365 once released.\n  libraryVariants.all {\n    it.generateBuildConfig.enabled = false\n  }\n}\n\ntasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {\n  kotlinOptions {\n    freeCompilerArgs = ['-Xno-param-assertions']\n  }\n}\n\napply from: rootProject.file('gradle/gradle-mvn-push.gradle')\n"
  },
  {
    "path": "sqlbrite-kotlin/gradle.properties",
    "content": "POM_ARTIFACT_ID=sqlbrite-kotlin\nPOM_NAME=SqlBrite (Kotlin Extensions)\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "sqlbrite-kotlin/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"com.squareup.sqlbrite2.kotlin\"/>\n"
  },
  {
    "path": "sqlbrite-kotlin/src/main/java/com/squareup/sqlbrite3/extensions.kt",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n@file:Suppress(\"NOTHING_TO_INLINE\") // Extensions provided for intentional convenience.\n\npackage com.squareup.sqlbrite3\n\nimport android.database.Cursor\nimport android.support.annotation.RequiresApi\nimport com.squareup.sqlbrite3.BriteDatabase.Transaction\nimport com.squareup.sqlbrite3.SqlBrite.Query\nimport io.reactivex.Observable\nimport java.util.Optional\n\ntypealias Mapper<T> = (Cursor) -> T\n\n/**\n * Transforms an observable of single-row [Query] to an observable of `T` using `mapper`.\n *\n * It is an error for a query to pass through this operator with more than 1 row in its result set.\n * Use `LIMIT 1` on the underlying SQL query to prevent this. Result sets with 0 rows do not emit\n * an item.\n *\n * This operator ignores null cursors returned from [Query.run].\n *\n * @param mapper Maps the current [Cursor] row to `T`. May not return null.\n */\ninline fun <T> Observable<Query>.mapToOne(noinline mapper: Mapper<T>): Observable<T>\n    = lift(Query.mapToOne(mapper))\n\n/**\n * Transforms an observable of single-row [Query] to an observable of `T` using `mapper`\n *\n * It is an error for a query to pass through this operator with more than 1 row in its result set.\n * Use `LIMIT 1` on the underlying SQL query to prevent this. Result sets with 0 rows emit\n * `default`.\n *\n * This operator emits `defaultValue` if null is returned from [Query.run].\n *\n * @param mapper Maps the current [Cursor] row to `T`. May not return null.\n * @param default Value returned if result set is empty\n */\ninline fun <T> Observable<Query>.mapToOneOrDefault(default: T, noinline mapper: Mapper<T>): Observable<T>\n    = lift(Query.mapToOneOrDefault(mapper, default))\n\n/**\n * Transforms an observable of single-row [Query] to an observable of `T` using `mapper.\n *\n * It is an error for a query to pass through this operator with more than 1 row in its result set.\n * Use `LIMIT 1` on the underlying SQL query to prevent this. Result sets with 0 rows emit\n * `default`.\n *\n * This operator ignores null cursors returned from [Query.run].\n *\n * @param mapper Maps the current [Cursor] row to `T`. May not return null.\n */\n@RequiresApi(24)\ninline fun <T> Observable<Query>.mapToOptional(noinline mapper: Mapper<T>): Observable<Optional<T>>\n    = lift(Query.mapToOptional(mapper))\n\n/**\n * Transforms an observable of [Query] to `List<T>` using `mapper` for each row.\n *\n * Be careful using this operator as it will always consume the entire cursor and create objects\n * for each row, every time this observable emits a new query. On tables whose queries update\n * frequently or very large result sets this can result in the creation of many objects.\n *\n * This operator ignores null cursors returned from [Query.run].\n *\n * @param mapper Maps the current [Cursor] row to `T`. May not return null.\n */\ninline fun <T> Observable<Query>.mapToList(noinline mapper: Mapper<T>): Observable<List<T>>\n    = lift(Query.mapToList(mapper))\n\n/**\n * Run the database interactions in `body` inside of a transaction.\n *\n * @param exclusive Uses [BriteDatabase.newTransaction] if true, otherwise\n * [BriteDatabase.newNonExclusiveTransaction].\n */\ninline fun <T> BriteDatabase.inTransaction(\n    exclusive: Boolean = true,\n    body: BriteDatabase.(Transaction) -> T\n): T {\n  val transaction = if (exclusive) newTransaction() else newNonExclusiveTransaction()\n  try {\n    val result = body(transaction)\n    transaction.markSuccessful()\n    return result\n  } finally {\n    transaction.end()\n  }\n}\n"
  },
  {
    "path": "sqlbrite-lint/build.gradle",
    "content": "apply plugin: 'kotlin'\n\ndependencies {\n  compileOnly rootProject.ext.kotlinStdLib\n  compileOnly rootProject.ext.lintApi\n\n  testImplementation rootProject.ext.junit\n  testImplementation rootProject.ext.lint\n  testImplementation rootProject.ext.lintTests\n}\n\njar {\n  manifest {\n    attributes(\"Lint-Registry-v2\": \"com.squareup.sqlbrite3.BriteIssueRegistry\")\n  }\n}\n"
  },
  {
    "path": "sqlbrite-lint/src/main/java/com/squareup/sqlbrite3/BriteIssueRegistry.kt",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3\n\nimport com.android.tools.lint.client.api.IssueRegistry\n\nclass BriteIssueRegistry : IssueRegistry() {\n\n  override fun getIssues() = listOf(SqlBriteArgCountDetector.ISSUE)\n}\n"
  },
  {
    "path": "sqlbrite-lint/src/main/java/com/squareup/sqlbrite3/SqlBriteArgCountDetector.kt",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3\n\nimport com.android.tools.lint.detector.api.Category\nimport com.android.tools.lint.detector.api.ConstantEvaluator.evaluateString\nimport com.android.tools.lint.detector.api.Detector\nimport com.android.tools.lint.detector.api.Implementation\nimport com.android.tools.lint.detector.api.Issue\nimport com.android.tools.lint.detector.api.JavaContext\nimport com.android.tools.lint.detector.api.Scope.JAVA_FILE\nimport com.android.tools.lint.detector.api.Scope.TEST_SOURCES\nimport com.android.tools.lint.detector.api.Severity\nimport com.intellij.psi.PsiMethod\nimport org.jetbrains.uast.UCallExpression\nimport java.util.EnumSet\n\nprivate const val BRITE_DATABASE = \"com.squareup.sqlbrite3.BriteDatabase\"\nprivate const val QUERY_METHOD_NAME = \"query\"\nprivate const val CREATE_QUERY_METHOD_NAME = \"createQuery\"\n\nclass SqlBriteArgCountDetector : Detector(), Detector.UastScanner {\n\n  companion object {\n\n    val ISSUE: Issue = Issue.create(\n        \"SqlBriteArgCount\",\n        \"Number of provided arguments doesn't match number \" +\n            \"of arguments specified in query\",\n        \"When providing arguments to query you need to provide the same amount of \" +\n            \"arguments that is specified in query.\",\n        Category.MESSAGES,\n        9,\n        Severity.ERROR,\n        Implementation(SqlBriteArgCountDetector::class.java, EnumSet.of(JAVA_FILE, TEST_SOURCES)))\n  }\n\n  override fun getApplicableMethodNames() = listOf(CREATE_QUERY_METHOD_NAME, QUERY_METHOD_NAME)\n\n  override fun visitMethod(context: JavaContext, call: UCallExpression, method: PsiMethod) {\n    val evaluator = context.evaluator\n\n    if (evaluator.isMemberInClass(method, BRITE_DATABASE)) {\n      // Skip non varargs overloads.\n      if (!method.isVarArgs) return\n\n      // Position of sql parameter depends on method.\n      val sql = evaluateString(context,\n          call.valueArguments[if (call.isQueryMethod()) 0 else 1], true) ?: return\n\n      // Count only vararg arguments.\n      val argumentsCount = call.valueArgumentCount - if (call.isQueryMethod()) 1 else 2\n      val questionMarksCount = sql.count { it == '?' }\n      if (argumentsCount != questionMarksCount) {\n        val requiredArguments = \"$questionMarksCount ${\"argument\".pluralize(questionMarksCount)}\"\n        val actualArguments = \"$argumentsCount ${\"argument\".pluralize(argumentsCount)}\"\n        context.report(ISSUE, call, context.getLocation(call), \"Wrong argument count, \" +\n            \"query $sql requires $requiredArguments, but was provided $actualArguments\")\n      }\n    }\n  }\n\n  private fun UCallExpression.isQueryMethod() = methodName == QUERY_METHOD_NAME\n\n  private fun String.pluralize(count: Int) = if (count == 1) this else this + \"s\"\n}"
  },
  {
    "path": "sqlbrite-lint/src/test/java/com/squareup/sqlbrite3/SqlBriteArgCountDetectorTest.kt",
    "content": "/*\n * Copyright (C) 2017 Square, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.squareup.sqlbrite3\n\nimport com.android.tools.lint.checks.infrastructure.TestFiles.java\nimport com.android.tools.lint.checks.infrastructure.TestLintTask.lint\nimport org.junit.Test\n\nclass SqlBriteArgCountDetectorTest {\n\n  companion object {\n    private val BRITE_DATABASE_STUB = java(\n        \"\"\"\n      package com.squareup.sqlbrite3;\n\n      public final class BriteDatabase {\n\n        public void query(String sql, Object... args) {\n        }\n\n        public void createQuery(String table, String sql, Object... args) {\n        }\n\n        // simulate createQuery with SupportSQLiteQuery query parameter\n        public void createQuery(String table, int something) {\n        }\n      }\n      \"\"\".trimIndent()\n    )\n  }\n\n  @Test\n  fun cleanCaseWithWithQueryAsLiteral() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n                  private static final String QUERY = \"SELECT name FROM table WHERE id = ?\";\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.query(QUERY, \"id\");\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expectClean()\n  }\n\n  @Test\n  fun cleanCaseWithQueryAsBinaryExpression() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n                  private static final String QUERY = \"SELECT name FROM table WHERE \";\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.query(QUERY + \"id = ?\", \"id\");\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expectClean()\n  }\n\n  @Test\n  fun cleanCaseWithQueryThatCantBeEvaluated() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n                  private static final String QUERY = \"SELECT name FROM table WHERE id = ?\";\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.query(query(), \"id\");\n                  }\n\n                  private String query() {\n                    return QUERY + \" age = ?\";\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expectClean()\n  }\n\n  @Test\n  fun cleanCaseWithNonVarargMethodCall() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.createQuery(\"table\", 42);\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expectClean()\n  }\n\n  @Test\n  fun queryMethodWithWrongNumberOfArguments() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n                  private static final String QUERY = \"SELECT name FROM table WHERE id = ?\";\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.query(QUERY);\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expect(\"src/test/pkg/Test.java:10: \" +\n            \"Error: Wrong argument count, query SELECT name FROM table WHERE id = ?\" +\n            \" requires 1 argument, but was provided 0 arguments [SqlBriteArgCount]\\n\" +\n            \"      db.query(QUERY);\\n\" +\n            \"      ~~~~~~~~~~~~~~~\\n\" +\n            \"1 errors, 0 warnings\")\n  }\n\n  @Test\n  fun createQueryMethodWithWrongNumberOfArguments() {\n    lint().files(\n        BRITE_DATABASE_STUB,\n        java(\n            \"\"\"\n              package test.pkg;\n\n              import com.squareup.sqlbrite3.BriteDatabase;\n\n              public class Test {\n                  private static final String QUERY = \"SELECT name FROM table WHERE id = ?\";\n\n                  public void test() {\n                    BriteDatabase db = new BriteDatabase();\n                    db.createQuery(\"table\", QUERY);\n                  }\n\n              }\n            \"\"\".trimIndent()))\n        .issues(SqlBriteArgCountDetector.ISSUE)\n        .run()\n        .expect(\"src/test/pkg/Test.java:10: \" +\n            \"Error: Wrong argument count, query SELECT name FROM table WHERE id = ?\" +\n            \" requires 1 argument, but was provided 0 arguments [SqlBriteArgCount]\\n\" +\n            \"      db.createQuery(\\\"table\\\", QUERY);\\n\" +\n            \"      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\\n\" +\n            \"1 errors, 0 warnings\")\n  }\n}"
  }
]