Giter Site home page Giter Site logo

metabase / toucan Goto Github PK

View Code? Open in Web Editor NEW
569.0 48.0 52.0 687 KB

A classy high-level Clojure library for defining application models and retrieving them from a DB

License: Eclipse Public License 1.0

Emacs Lisp 0.46% Clojure 99.33% Shell 0.21%
toucan hydration honeysql clojure orm metabase flexible sql

toucan's Introduction

Downloads Dependencies Status Circle CI License cljdoc badge

Clojars Project

Warning

This version of Toucan is deprecated. Please use Toucan 2 instead; it's a complete rewrite with many improvements over Toucan 1. If you're already using Toucan 1, check out the toucan2-toucan1 migration helper.

Toucan

Toucan

Overview

There are no SQL/Relational DB ORMs for Clojure for obvious reasons. -- Andrew Brehaut

Toucan provides the better parts of an ORM for Clojure, like simple DB queries, flexible custom behavior when inserting or retrieving objects, and easy hydration of related objects, all in a powerful and classy way.

Toucan builds on top of clojure.java.jdbc and the excellent HoneySQL. The code that inspired this library was originally written to bring some of the sorely missed conveniences of Korma to HoneySQL when we transitioned from the former to the latter at Metabase. Over the last few years, I've continued to build upon and refine the interface of Toucan, making it simpler, more powerful, and more flexible, all while maintaining the function-based approach of HoneySQL.

View the complete documentation here, or continue below for a brief tour.

Simple Queries:

Toucan greatly simplifies the most common queries without limiting your ability to express more complicated ones. Even something relatively simple can take quite a lot of code to accomplish in HoneySQL:

clojure.java.jdbc + HoneySQL

;; select the :name of the User with ID 100
(-> (jdbc/query my-db-details
       (sql/format
         {:select [:name]
          :from   [:user]
          :where  [:= :id 100]
          :limit  1}
         :quoting :ansi))
    first
    :name)

Toucan

;; select the :name of the User with ID 100
(db/select-one-field :name User :id 100)

Toucan keeps the simple things simple. Read more about Toucan's database functions here.

Flexible custom behavior when inserting and retrieving objects:

Toucan makes it easy to define custom behavior for inserting, retrieving, updating, and deleting objects on a model-by-model basis. For example, suppose you want to convert the :status of a User to a keyword whenever it comes out of the database, and back into a string when it goes back in:

clojure.java.jdbc + HoneySQL

;; insert new-user into the DB, converting the value of :status to a String first
(let [new-user {:name "Cam", :status :new}]
  (jdbc/insert! my-db-details :user (update new-user :status name)))

;; fetch a user, converting :status to a Keyword
(-> (jdbc/query my-db-details
      (sql/format
        {:select [:*]
         :from   [:user]
         :where  [:= :id 200]}))
    first
    (update :status keyword))

Toucan

With Toucan, you just need to define the model, and tell that you want :status automatically converted:

;; define the User model
(defmodel User :user
  IModel
  (types [this] ;; tell Toucan to automatically do Keyword <-> String conversion for :status
    {:status :keyword}))

After that, whenever you fetch, insert, or update a User, :status will automatically be converted appropriately:

;; Insert a new User
(db/insert! User :name "Cam", :status :new) ; :status gets stored in the DB as "new"

;; Fetch User 200
(User 200) ; :status is converted to a keyword when User is fetched

Read more about defining and customizing the behavior of models here.

Easy hydration of related objects

If you're developing something like a REST API, there's a good chance at some point you'll want to hydrate some related objects and return them in your response. For example, suppose we wanted to return a Venue with its Category hydrated:

;; No hydration
{:name        "The Tempest"
 :category_id 120
 ...}

;; w/ hydrated :category
{:name        "The Tempest"
 :category_id 120
 :category    {:name "Dive Bar"
               ...}
 ...}

The code to do something like this is enormously hairy in vanilla JDBC + HoneySQL. Luckily, Toucan makes this kind of thing a piece of cake:

(hydrate (Venue 120) :category)

Toucan is clever enough to automatically figure out that Venue's :category_id corresponds to the :id of a Category, and constructs efficient queries to fetch it. Toucan can hydrate single objects, sequences of objects, and even objects inside hydrated objects, all in an efficient way that minimizes the number of DB calls. You can also define custom functions to hydrate different keys, and add additional mappings for automatic hydration. Read more about hydration here.

Getting Started

To get started with Toucan, all you need to do is tell it how to connect to your DB by calling set-default-db-connection!:

(require '[toucan.db :as db])

(db/set-default-db-connection!
  {:classname   "org.postgresql.Driver"
   :subprotocol "postgresql"
   :subname     "//localhost:5432/my_db"
   :user        "cam"})

Pass it the same connection details that you'd pass to any clojure.java.jdbc function; it can be a simple connection details map like the example above or some sort of connection pool. This function only needs to be called once, and can done in your app's normal entry point (such as -main) or just as a top-level function call outside any other function in a namespace that will get loaded at launch.

Read more about setting up the DB and configuring options such as identifier quoting style here.

Test Utilities

Toucan provides several utility macros that making writing tests easy. For example, you can easily create temporary objects so your tests don't affect the state of your test DB:

(require '[toucan.util.test :as tt])

;; create a temporary Venue with the supplied values for use in a test.
;; the object will be removed from the database afterwards (even if the macro body throws an Exception)
;; which makes it easy to write tests that don't change the state of the DB
(expect
  "hos_bootleg_tavern"
  (tt/with-temp Venue [venue {:name "Ho's Bootleg Tavern"}]
    (venue-slug venue))

Read more about Toucan test utilities here.

Annotated Source Code

View the annotated source code here, generated with Marginalia.

Contributing

Pull requests for bugfixes, improvements, more documentaton, and other enhancements are always welcome. Toucan code is written to the strict standards of the Metabase Clojure Style Guide, so take a moment to familiarize yourself with the style guidelines before submitting a PR.

If you're interested in contributing, be sure to check out issues tagged "help wanted". There's lots of ways Toucan can be improved and we need people like you to make it even better.

Tests & Linting

Before submitting a PR, you should also make sure tests and the linters pass. You can run tests and linters as follows:

lein test && lein lint

Tests assume you have Postgres running locally and have a test DB set up with read/write permissions. Toucan will populate this database with appropriate test data.

If you don't have Postgres running locally, you can instead the provided shell script to run Postgres via docker. Use lein start-db to start the database and lein stop-db to stop it. Note the script is a Bash script and won't work on Windows unless you're using the Windows Subsystem for Linux.

To configure access to this database, set the following env vars as needed:

Env Var Default Value Notes
TOUCAN_TEST_DB_HOST localhost
TOUCAN_TEST_DB_PORT 5432
TOUCAN_TEST_DB_NAME toucan_test
TOUCAN_TEST_DB_USER Optional when running Postgres locally
TOUCAN_TEST_DB_PASS Optional when running Postgres locally

These tests and linters also run on CircleCI for all commits and PRs.

We try to keep Toucan well-tested, so new features should include new tests for them; bugfixes should include failing tests. Make sure to properly document new features as well! ๐Ÿ˜‹

A few more things: please carefully review your own changes and revert any superfluous ones. (A good example would be moving words in the Markdown documentation to different lines in a way that wouldn't change how the rendered page itself would appear. These sorts of changes make a PR bigger than it needs to be, and, thus, harder to review.) And please include a detailed explanation of what changes you're making and why you've made them. This will help us understand what's going on while we review it. Thanks! ๐Ÿ˜ป

YourKit

YourKit

YourKit has kindly given us an open source license for their profiler, helping us profile and improve Toucan performance.

YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of YourKit Java Profiler, YourKit .NET Profiler, and YourKit YouMonitor.

License

Code and documentation copyright ยฉ 2018-2019 Metabase, Inc. Artwork copyright ยฉ 2018-2019 Cam Saul.

Distributed under the Eclipse Public License, same as Clojure.

toucan's People

Contributors

andrethehunter avatar axrs avatar bennyandresen avatar camsaul avatar dcreemer avatar dpsutton avatar elizabethforest avatar evilsneer avatar marksto avatar miikka avatar pawit-metabase avatar plexus avatar quan-nh avatar senior avatar thingographist avatar tsmacdonald avatar walterl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

toucan's Issues

Allow extending protocols right from defmodel

The current way of specifying hydration keys is rather unwieldy, so I came up with this

(defmodel Video :videos
  models/IModel
  (hydration-keys [_]
    [:video :preview_video]))

I have a working patch (see this gist). Happy to submit it, just wanted to check first if this is something you'd be interested in.

No implementation of method: :post-insert of protocol: #'toucan.models/IModel found for class: nil

based on metabase/metabase#6295 and my own experience trying to get Metabase to connect to a pgpool-managed Postgresql cluster, it seems like there is a problem with how Toucan builds SQL queries, which leads to the above error when such queries are executed against pgpool.

Maybe Toucan is trying to leverage a feature of Postgres that pgpool doesn't support?

Or perhaps Toucan is working correctly but Metabase is not implementing it right?

Params in query call possible ?

I have been looking at the code here

(defn query
from what I can tell query does not take :params to pass in parameters ? or else I am not understanding whats going on.

Tried a few things like

(db/query honey-sql-query :search "text")

But this does not work, the doc string says it takes a honey sql form which i Take it is just the hash map form before its passed to sql/format which has a :params keyword to allow you to injected name params into your query.

@camsaul is this possible am i missing something, or would toucan need to be extended to allow for this ?

Add an `upsert!` function

Right now to do an "upsert" operation you have to do

(or (db/update! ...) ; update! returns truthy value iff something was updated
    (db/insert!))

However, that is subject to race conditions ๐Ÿ™€

It would be nice to simplify this common pattern and avoid the race conditions with an upsert! function or something like that. (suggestion credit: @thearthur)

Add ability to designate a function for DB setup & automatically run it

Slightly inconvenient that we have to call appropriate DB setup functions when using from the REPL, e.g. in Metabase:

(metabase.db/setup-db-if-needed!)

It would be more convenient if we could tag a certain function as the DB setup function:

(defn ^:toucan/setup-fn setup-db-if-needed! []
 ...)

and have Toucan automatically find and run this function using the namespace tools if the DB is not yet set up.

Select query does not work with sub-query

@camsaul

Example query:

(db/select [User :first-name :last-name]
            :id [:in {:select [:id] :from [User]}]
            {:order-by [:id]})

Error:

  act-msg: exception in actual: (db/select [User :first-name :last-name] :id [:in {:select [:id], :from [User]}] {:order-by [:id]})
    threw: class org.postgresql.util.PSQLException - ERROR: column "select" does not exist
  Position: 80
           org.postgresql.core.v3.QueryExecutorImpl$receiveErrorResponse (QueryExecutorImpl.java:2497)
           org.postgresql.core.v3.QueryExecutorImpl$processResults (QueryExecutorImpl.java:2233)
           org.postgresql.core.v3.QueryExecutorImpl$execute (QueryExecutorImpl.java:310)
           org.postgresql.jdbc.PgStatement$executeInternal (PgStatement.java:446)
           org.postgresql.jdbc.PgStatement$execute (PgStatement.java:370)
           org.postgresql.jdbc.PgPreparedStatement$executeWithFlags (PgPreparedStatement.java:149)
           org.postgresql.jdbc.PgPreparedStatement$executeQuery (PgPreparedStatement.java:108)
           clojure.java.jdbc$execute_query_with_params$invokeStatic (jdbc.clj:1079)
           on (jdbc.clj:1073)
           clojure.java.jdbc$db_query_with_resultset_STAR_$invokeStatic (jdbc.clj:1102)
           on (jdbc.clj:1082)
           clojure.java.jdbc$query$invokeStatic (jdbc.clj:1171)
           on (jdbc.clj:1133)
           toucan.db$query$invokeStatic (db.clj:308)
           toucan.db$query$doInvoke (db.clj:304)
           toucan.db$simple_select$invokeStatic (db.clj:414)
           on (db.clj:403)
           toucan.db$select$invokeStatic (db.clj:708)
           toucan.db$select$doInvoke (db.clj:702)

This issue was introduced in #87. A variant of this was reported in #88 and fixed in #89.

A workaround is to use an explicit {:where [:in :id {:select [:id] :from [User]}]} instead.

Does Toucan support enumerated types?

@camsaul My postgres db has an enumerated type. I see this possible hack for making that work in java-jdbc, but that looks messy and possibly destroys the type safety of the enum in the first place (not clear). I can probably live with just strings here, but any advice on making enums work with Toucan?

Transactions do not work with *db-connection*

The way I'm using Toucan is that I never call set-default-db-connection! and instead I always set *db-connection*. I'm using Integrant and explicitly passing the database connection fits that model better than having global state.

I just noticed that transactions do not work with *db-connection* โ€“ if you use toucan.db/transaction, the code inside the macro will not be run inside a transaction. This is because toucan.db/connection always returns *db-connection* if it's set:

toucan/src/toucan/db.clj

Lines 124 to 127 in c5567c8

(or *db-connection*
*transaction-connection*
@default-db-connection
(throw (Exception. "DB is not set up. Make sure to call set-default-db-connection! or bind *db-connection*."))))

I'm not sure if this is a bug or an intentional feature, but I certainly expected transactions to work. As a workaround, I'm now binding toucan.db/*transaction-connection* (which is marked as private) instead of *db-connection* and that works fine.

Use multimethods for custom hydration functions

Instead of tagging them with metadata it would be better if we could make them multimethods that dispatch off of the model as well as the key they are hydrating. That way different methods could be used to hydrate the same key for instances of different models. The approach also seems a little cleaner than having hydrate look through all namespaces for functions with matching metadata

There are two things that might make this tricky:

  • The difference between single hydration and batched hydration functions would have to be addressed somehow (perhaps with two multimethods?)
  • This change would require reworking things a bit for people using the library

Scanning for hydration keys does a keyword call on every var

The require-model-namespaces-and-find-hydration-keys function loops over every var in every namespace to check if it's a model. To do this it calls :toucan.models/model (via toucan.models/model?) on the var's value.

(:toucan.models/model val)

This is problematic since that value could be anything, and calling it like this with a keyword could have all kinds of side effects.

To make this concrete, say Datomic is present and loaded in the same process as Toucan, then it will call

(toucan.models/model? datomic.pull/normalized-pattern-cache)

Which raises an exception, making Toucan unusable.

@camsaul I'm not sure what to propose as the best solution here, in my project I've monkey-patched require-model-namespaces-and-find-hydration-fns to call (and (record? model) (models/model? model)). Would that be acceptable?

insert! returns differently between models

Hi, so I have two models here, lets say one is Model A and the other one is Model B. When I insert! Model A, the function returns its identity, but when I insert! Model B, the returned value is nil.

The difference between this model is that Model A is using generated id from mysql, where Model B is using id composed in my application:

(defmodel B :b
  IModel
  (pre-insert [payload]
              (assoc payload
                     :id (.toString (java.util.UUID/randomUUID))))

=> (insert! A {:name "John Doe" :email "[email protected]"}
#AInstance{:id 1 :name "John Doe" :email "[email protected]"}

=> (insert! B {:name "John Doe" :email "[email protected]"}
nil

Is this behavior expected?

Better support for objects that don't have a primary key called :id

I think we could add a method to IModel called primary-key or something like that that could be used to get field(s) that should be used as the primary key (default implementation would return :id). It could accept either a keyword (for a single-column key) or a sequence of keywords for multiple-column keys.

Add lazy/streaming versions of functions like select

When fetching huge numbers of rows this would certainly be a nice option to have. Whether we take advantage of DB cursors, or perhaps come up with some method to do a series of requests with paging, or some other method, this would prevent us from having to drop down to low-level clojure.java.jdbc queries for these sorts of things.

Will not use Transaction when call db/insert with a binding to *db-connection*

@camsaul
We found when call db/transaction with a binding db-connection, it will never work, since the
connection function will always return db-connection. What I suggest is to adjust the priority to move transaction-connection to the top. I do some testing in my local env, it works.
(defn connection []
(or transaction-connection ;;fix the bug move transaction to the top, otherwise transaction will not work.
db-connection
@default-db-connection
(throw (Exception. "DB is not set up. Make sure to call set-default-db-connection! or bind db-connection.")))))))

Should atoms in toucan.hydrate flushed after defmodel?

When we change models, we may change hydration-keys, this change will not work unless we reload toucan.hydrate namespace. This looks inconvenient in development.

Since automagic-batched-hydration-key->model and automagic-batched-hydration-keys are delays, why not reload them after each time we call defmodel? maybe there's a better way for this?

Hydration Functions are Too Eager

I've come across a situation with lazy model loading not updating simple hydration functions.

  • Define three models (user, permissions, company)
  • The company model definition defines a simple hydration for users (in the same company.clj file).
  • The user model definition defines a simple hydration fo permissions (in the same user.clj file).

In the main execution path, we can select a single company, then invoke hydrate for users. The users model will be lazily loaded and hydrated. Awesome.

During the loading of users, a new ^:hydration function is loaded for the permissions. Unfortunately, the simple hydration method has already realized the delay; and thus doesn't know of the new permissions hydration function.

The fix I've come across is to put hydrations into a seperate file and ensure it's required by what needs it. This however feels counter intuitive when models can be resolved when required.

Thoughts?

Model instance to map

hi @camsaul, I'm new to clojure, I'm trying to use toucan to build a rest api, but I can't find a way to respond to a request with a map that represents a record on my database, I have the following code:

(defn dbh [req]
  (let [user (User 3)]
    {:status 200
     :headers {"Content-Type" "application/json"}
     :body user  })) 

but when I make a request i just get what seems to me (I don't know clojure) like a sequence of pairs

[:id 3][:name "wayne"][:email "[email protected]"][:password "ImFzZGFzZCI=.5lcSa6XsqitTjnXVD3BSGptcVbQ="]

could you please point me to the right direction to get a proper response?

Setting :identifiers for JDBC connection is broken with toucan.db/query in 1.13.0

We're setting :identifiers for our JDBC connection to a function that converts underscores to dashes. However, Toucan 1.13.0 introduced the this change to toucan.db/query which overrides our identifiers function.

Now our underscores do not get transformed to dashes, which of course breaks many things, and there's no workaround. I think that the best thing would be to not set :identifiers here and just let clojure.java.jdbc handle it once the version with the fix has been released.

@camsaul

apply-type-fns does not apply the function if the value is false

Hi @camsaul

I am attempting to run a type function, when the column value is false the function does not run, as per below.

(toucan.models/add-type! :demo
                         :in (fn [val]
                               (println "Demo type called")
                               (let [n-val (if val "True Transformed" "False Transformed")]
                                 n-val))
                         :out boolean)


(extend (class Venue)
  toucan.models/IModel
  (merge toucan.models/IModelDefaults {:default-fields (constantly #{:id :name :category})
                                :types          (constantly {:category :keyword
                                                             :demo :demo
                                                             }
                                                            )
                                :properties     (constantly {:timestamped? true})}))

(clojure.test/is (=
 #toucan.test_models.venue.VenueInstance{:demo "True Transformed"}
 (#'toucan.models/apply-type-fns #toucan.test_models.venue.VenueInstance{:demo "A"} :in)))
;=> true
(clojure.test/is (=
 #toucan.test_models.venue.VenueInstance{:demo "False Transformed"}
 (#'toucan.models/apply-type-fns #toucan.test_models.venue.VenueInstance{:demo nil} :in)))
;=> false
(clojure.test/is (=
 #toucan.test_models.venue.VenueInstance{:demo "True Transformed"}
 (#'toucan.models/apply-type-fns #toucan.test_models.venue.VenueInstance{:demo true} :in)))
;=> true
(clojure.test/is (=
 #toucan.test_models.venue.VenueInstance{:demo "False Transformed"}
 (#'toucan.models/apply-type-fns #toucan.test_models.venue.VenueInstance{:demo false} :in)))
;=> false
`

Add ns qualified keyword options to IModel

@camsaul

Qualified keywords are pretty common in Clojure, and either inferring them from table names or providing them per-model would be nice to have.

I'm using next.jdbc right now, and it's handling a lot of that for me.

Need tests for a model with byte array PK

should be possible to automatically convert between hex (in Clojure) and byte array in DB. Currently it seems like certain functions like update! don't support this.

Migrations?

Hi,

How do you recommend doing migrations with toucan?
Are we supposed to use a separate library for that?

@camsaul

Any Plans or Interest in Moving to HoneySQL 2?

Hi @camsaul!

I'm working on a project that uses Toucan and HoneySQL 2.x. I've run into a couple bugs where people try to feed a structured HoneySQL 2 query into Toucan. It's not a huge deal, but...

I was wondering: are there any plans (or even interest) in moving to HoneySQL 2?

Toucan put a string as table name in sql using mysql

Hi @camsaul,
I'm playing with Toucan, connecting to a mysql db.
I configured Toucan to work with hikari datasource and mount:

(ns reviews.database
  (:require [hikari-cp.core :as h]
            [mount.core :refer [defstate]]
            [toucan.models :refer [set-root-namespace!]]
            [toucan.db :refer [set-default-db-connection!]]))

;move to configuration
(def datasource-options {:auto-commit        true
                         :read-only          false
                         :connection-timeout 30000
                         :validation-timeout 5000
                         :idle-timeout       600000
                         :max-lifetime       1800000
                         :minimum-idle       1
                         :maximum-pool-size  10
                         :pool-name          "db-pool"
                         :adapter            "mysql"
                         :username           "root"
                         :password           "root"
                         :database-name      "reviews-app"
                         :server-name        "localhost"
                         :port-number        3306
                         :register-mbeans    false})

(defn- create-datasource []
    (doto {:datasource (h/make-datasource datasource-options)}
          (set-default-db-connection!)))

(defstate database 
    :start (create-datasource)
    :stop (h/close-datasource (database :datasource)))

(set-root-namespace! 'reviews.models)

Having my model:

(ns reviews.models.review
    (:require [toucan.models :refer [defmodel]]))

(defmodel Review :reviews)

When I do:

user=> (t/debug-print-queries 
         (t/select 'Review))
;output
; {:select [:*],
;  :from [{:table :reviews, :name "Review", :toucan.models/model true}]}
; nil 
; SELECT *
; FROM "reviews"
; null

I get the following exception:

Execution error (MySQLSyntaxErrorException) at
jdk.internal.reflect.NativeConstructorAccessorImpl/newInstance0 (NativeConstructorAccessorImpl.java:-2).

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"reviews"' at line 1
class com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException

The issue is that it writes the table name as "reviews" instead of review.
Have I misconfigured something or is this some sort of bug?

Rollbacks in db/transaction

@camsaul I have been trying to use transactions with toucan, however I noticed with test my transaction are not being rolled back.

In my case i am running tests and wanting the data to not be committed and just be rolled back at the end, I am using this code.

   (db/transaction
    (jdbc/db-set-rollback-only! (db/connection))
   (db/insert! User :username "test_user" :password "test" :email "[email protected]")
    (f))

Digging into the src i noticed the rollback code was missing, is this intentional or any possibility of support rollback when things fail, I am not sure of the implications of just dropping in the rollback expression into the db/transact wrapper.

Update! does not work with non-integer ids

@camsaul

When testing my application I get at java.lang.AssertionError: Assert failed: (integer? id) for toucan.db/update!. The :pre condition assumes the id is integer, however we use UUIDs.

I've created a PR: #19

Also, I can not run the tests locally. I get a org.postgresql.util.PSQLException: The server requested password-based authentication, but no password was provided. error. I think a local testing db is needed (a toucan_test database?). I'd recommend setting up a global fixture that creates the db if required.

Also, switch to CircleCI 2.0 https://circleci.com/docs/2.0/migrating-from-1-2/, then the same docker container used for the testing database could be run locally.

Select queries do not work with custom types

Hi @camsaul! A regression issue was introduced with the latest library version upgrade, I guess. (Or probably I didn't notice this one before.) Select queries simply do not work with custom types, e.g. native PostgreSQL ENUM type. There is an exception that happens during the where+ clause construction.

class java.lang.ClassCastException - class clojure.lang.PersistentVector cannot be cast to class clojure.lang.Named (clojure.lang.PersistentVector and clojure.lang.Named are in unnamed module of loader 'app')
           toucan.test_models.pg_enum$kwd__GT_pg_enum$invokeStatic (pg_enum.clj:17)
           on (pg_enum.clj:15)
           toucan.models$do_type_fn$invokeStatic (models.clj:358)
           on (models.clj:354)
           toucan.db$where_PLUS_$invokeStatic (db.clj:463)
           on (db.clj:453)
           toucan.db$select_one$invokeStatic (db.clj:628)
...

So the fact is the current where+ fn implementation doesn't cater for the things like [:= <custom_type_value>].

Will provide you with a fix soon. Had to develop one during the weekend.

Cheers!

Working with Postgres Jsonb

I'm trying to integrate Toucan into a project that uses Postgres, but I can't insert into a Jsonb column. Here's the model:

(m/add-type! :json
  :in json/generate-string
  :out #(json/parse-string % keyword)) 

(m/defmodel Integration :integrations
  m/IModel
  (m/types [_]
    {:data :json}))  

And here's the error:

user=> (db/insert! Integration {:registration "123" :data {"a" "b"}})
(db/insert! Integration {:registration "123" :data {"a" "b"}})
2019-04-09 19:04:13,218 [nRepl-session-f8c664bb-fa71-4a5c-b061-be9ef18ff331] DEBUG toucan.db - DB Call: INSERT INTO "integrations" ("registration", "data") VALUES (?, ()) 
Execution error (PSQLException) at org.postgresql.core.v3.QueryExecutorImpl/receiveErrorResponse (QueryExecutorImpl.java:2477).
ERROR: syntax error at or near ")"
  Position: 66

I've followed the docs to the letter, but it doesn't work. Thanks!

@camsaul

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.