diff --git a/project.clj b/project.clj index 666e4426448588c760e8725c9b91ee6ada8d20ea..7eca1755cfed6c2095c00a86b6ba104ee4d1d68d 100644 --- a/project.clj +++ b/project.clj @@ -88,7 +88,10 @@ :main ^:skip-aot metabase.core :manifest {"Liquibase-Package" "liquibase.change,liquibase.changelog,liquibase.database,liquibase.parser,liquibase.precondition,liquibase.datatype,liquibase.serializer,liquibase.sqlgenerator,liquibase.executor,liquibase.snapshot,liquibase.logging,liquibase.diff,liquibase.structure,liquibase.structurecompare,liquibase.lockservice,liquibase.sdk,liquibase.ext"} :target-path "target/%s" - :jvm-opts ["-server" ; Run JVM in server mode as opposed to client -- see http://stackoverflow.com/questions/198577/real-differences-between-java-server-and-java-client for a good explanation of this + :jvm-opts ["-XX:MaxPermSize=256m" ; give the JVM a little more PermGen space to avoid PermGen OutOfMemoryErrors + "-Xverify:none" ; disable bytecode verification when running in dev so it starts slightly faster + "-XX:+CMSClassUnloadingEnabled" ; let Clojure's dynamically generated temporary classes be GC'ed from PermGen + "-XX:+UseConcMarkSweepGC" ; Concurrent Mark Sweep GC needs to be used for Class Unloading (above) "-Djava.awt.headless=true"] ; prevent Java icon from randomly popping up in dock when running `lein ring server` :javac-options ["-target" "1.7", "-source" "1.7"] :uberjar-name "metabase.jar" @@ -122,10 +125,7 @@ :env {:mb-run-mode "dev"} :jvm-opts ["-Dlogfile.path=target/log" "-Xms1024m" ; give JVM a decent heap size to start with - "-Xmx2048m" ; hard limit of 2GB so we stop hitting the 4GB container limit on CircleCI - "-Xverify:none" ; disable bytecode verification when running in dev so it starts slightly faster - "-XX:+CMSClassUnloadingEnabled" ; let Clojure's dynamically generated temporary classes be GC'ed from PermGen - "-XX:+UseConcMarkSweepGC"] ; Concurrent Mark Sweep GC needs to be used for Class Unloading (above) + "-Xmx2048m"] ; hard limit of 2GB so we stop hitting the 4GB container limit on CircleCI :aot [metabase.logger]} ; Log appender class needs to be compiled for log4j to use it :reflection-warnings {:global-vars {*warn-on-reflection* true}} ; run `lein check-reflection-warnings` to check for reflection warnings :expectations {:injections [(require 'metabase.test-setup)] diff --git a/src/metabase/driver/postgres.clj b/src/metabase/driver/postgres.clj index 3e0351d501b704ec3fa486935530b0348b3cb046..320c22b8aca3206fbe7b5f43586a177af84d9022 100644 --- a/src/metabase/driver/postgres.clj +++ b/src/metabase/driver/postgres.clj @@ -114,8 +114,8 @@ :seconds (hsql/call :to_timestamp expr) :milliseconds (recur (hx// expr 1000) :seconds))) -(defn- date-trunc [unit expr] (hsql/call :date_trunc (hx/literal unit) expr)) -(defn- extract [unit expr] (hsql/call :extract unit expr)) +(defn- date-trunc [unit expr] (hsql/call :date_trunc (hx/literal unit) (hx/cast :timestamp expr))) +(defn- extract [unit expr] (hsql/call :extract unit (hx/cast :timestamp expr))) (def ^:private extract-integer (comp hx/->integer extract)) diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj index fc5301a071e2cf0fe33b5f5fa40833c11afb2919..ce3aea62759dca69bcff184f877ce5c2723470ac 100644 --- a/test/metabase/api/card_test.clj +++ b/test/metabase/api/card_test.clj @@ -211,11 +211,11 @@ ((user->client :rasta) :get 200 (str "card/" (u/get-id card)))) ;; Check that a user without permissions isn't allowed to fetch the card -(tt/expect-with-temp [Database [{database-id :id}] - Table [{table-id :id} {:db_id database-id}] - Card [card {:dataset_query (mbql-count-query database-id table-id)}]] +(expect "You don't have permissions to do that." - (do + (tt/with-temp* [Database [{database-id :id}] + Table [{table-id :id} {:db_id database-id}] + Card [card {:dataset_query (mbql-count-query database-id table-id)}]] ;; revoke permissions for default group to this database (perms/delete-related-permissions! (perms-group/all-users) (perms/object-path database-id)) ;; now a non-admin user shouldn't be able to fetch this card @@ -480,13 +480,14 @@ (db/delete! Card :id card-id))))) ;; Make sure we card creation fails if we try to set a `collection_id` we don't have permissions for -(tt/expect-with-temp [Collection [collection]] +(expect "You don't have permissions to do that." - ((user->client :rasta) :post 403 "card" {:name "My Cool Card" - :display "scalar" - :dataset_query (mbql-count-query (id) (id :venues)) - :visualization_settings {:global {:title nil}} - :collection_id (u/get-id collection)})) + (tt/with-temp Collection [collection] + ((user->client :rasta) :post 403 "card" {:name "My Cool Card" + :display "scalar" + :dataset_query (mbql-count-query (id) (id :venues)) + :visualization_settings {:global {:title nil}} + :collection_id (u/get-id collection)}))) ;; Make sure we can change the `collection_id` of a Card if it's not in any collection (tt/expect-with-temp [Card [card] @@ -497,26 +498,27 @@ (db/select-one-field :collection_id Card :id (u/get-id card)))) ;; Make sure we can still change *anything* for a Card if we don't have permissions for the Collection it belongs to -(tt/expect-with-temp [Collection [collection] - Card [card {:collection_id (u/get-id collection)}]] +(expect "You don't have permissions to do that." - ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:name "Number of Blueberries Consumed Per Month"})) + (tt/with-temp* [Collection [collection] + Card [card {:collection_id (u/get-id collection)}]] + ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:name "Number of Blueberries Consumed Per Month"}))) ;; Make sure that we can't change the `collection_id` of a Card if we don't have write permissions for the new collection -(tt/expect-with-temp [Collection [original-collection] - Collection [new-collection] - Card [card {:collection_id (u/get-id original-collection)}]] +(expect "You don't have permissions to do that." - (do + (tt/with-temp* [Collection [original-collection] + Collection [new-collection] + Card [card {:collection_id (u/get-id original-collection)}]] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) original-collection) ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)}))) ;; Make sure that we can't change the `collection_id` of a Card if we don't have write permissions for the current collection -(tt/expect-with-temp [Collection [original-collection] - Collection [new-collection] - Card [card {:collection_id (u/get-id original-collection)}]] +(expect "You don't have permissions to do that." - (do + (tt/with-temp* [Collection [original-collection] + Collection [new-collection] + Card [card {:collection_id (u/get-id original-collection)}]] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection) ((user->client :rasta) :put 403 (str "card/" (u/get-id card)) {:collection_id (u/get-id new-collection)}))) @@ -606,20 +608,22 @@ (POST-card-collections! :crowberto 200 new-collection [card-1 card-2])) ;; Test that we can bulk remove some Cards from a collection -(tt/expect-with-temp [Collection [collection] - Card [card-1 {:collection_id (u/get-id collection)}] - Card [card-2 {:collection_id (u/get-id collection)}]] +(expect [{:status "ok"} [nil nil]] - (POST-card-collections! :crowberto 200 nil [card-1 card-2])) + (tt/with-temp* [Collection [collection] + Card [card-1 {:collection_id (u/get-id collection)}] + Card [card-2 {:collection_id (u/get-id collection)}]] + (POST-card-collections! :crowberto 200 nil [card-1 card-2]))) ;; Check that we aren't allowed to move Cards if we don't have permissions for destination collection -(tt/expect-with-temp [Collection [collection] - Card [card-1] - Card [card-2]] +(expect ["You don't have permissions to do that." [nil nil]] - (POST-card-collections! :rasta 403 collection [card-1 card-2])) + (tt/with-temp* [Collection [collection] + Card [card-1] + Card [card-2]] + (POST-card-collections! :rasta 403 collection [card-1 card-2]))) ;; Check that we aren't allowed to move Cards if we don't have permissions for source collection (tt/expect-with-temp [Collection [collection] @@ -630,14 +634,14 @@ (POST-card-collections! :rasta 403 nil [card-1 card-2])) ;; Check that we aren't allowed to move Cards if we don't have permissions for the Card -(tt/expect-with-temp [Collection [collection] - Database [database] - Table [table {:db_id (u/get-id database)}] - Card [card-1 {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}] - Card [card-2 {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]] +(expect ["You don't have permissions to do that." [nil nil]] - (do + (tt/with-temp* [Collection [collection] + Database [database] + Table [table {:db_id (u/get-id database)}] + Card [card-1 {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}] + Card [card-2 {:dataset_query (mbql-count-query (u/get-id database) (u/get-id table))}]] (perms/revoke-permissions! (perms-group/all-users) (u/get-id database)) (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (POST-card-collections! :rasta 403 collection [card-1 card-2]))) diff --git a/test/metabase/api/collection_test.clj b/test/metabase/api/collection_test.clj index ab0e165b7ef06d1808e41d2f1a076e62f9ee9f90..e461c3bc0010030fa5ed4ed16e4d429952046ac5 100644 --- a/test/metabase/api/collection_test.clj +++ b/test/metabase/api/collection_test.clj @@ -18,27 +18,27 @@ ((user->client :crowberto) :get 200 "collection")) ;; check that we don't see collections if we don't have permissions for them -(tt/expect-with-temp [Collection [collection-1 {:name "Collection 1"}] - Collection [collection-2 {:name "Collection 2"}]] +(expect ["Collection 1"] - (do + (tt/with-temp* [Collection [collection-1 {:name "Collection 1"}] + Collection [collection-2 {:name "Collection 2"}]] (perms/grant-collection-read-permissions! (group/all-users) collection-1) (map :name ((user->client :rasta) :get 200 "collection")))) ;; check that we don't see collections if they're archived -(tt/expect-with-temp [Collection [collection-1 {:name "Archived Collection", :archived true}] - Collection [collection-2 {:name "Regular Collection"}]] +(expect ["Regular Collection"] - (do + (tt/with-temp* [Collection [collection-1 {:name "Archived Collection", :archived true}] + Collection [collection-2 {:name "Regular Collection"}]] (perms/grant-collection-read-permissions! (group/all-users) collection-1) (perms/grant-collection-read-permissions! (group/all-users) collection-2) (map :name ((user->client :rasta) :get 200 "collection")))) ;; Check that if we pass `?archived=true` we instead see archived cards -(tt/expect-with-temp [Collection [collection-1 {:name "Archived Collection", :archived true}] - Collection [collection-2 {:name "Regular Collection"}]] +(expect ["Archived Collection"] - (do + (tt/with-temp* [Collection [collection-1 {:name "Archived Collection", :archived true}] + Collection [collection-2 {:name "Regular Collection"}]] (perms/grant-collection-read-permissions! (group/all-users) collection-1) (perms/grant-collection-read-permissions! (group/all-users) collection-2) (map :name ((user->client :rasta) :get 200 "collection" :archived :true)))) @@ -100,7 +100,8 @@ {:name "My Beautiful Collection", :color "#ABCDEF"})) ;; check that non-admins aren't allowed to update a collection -(tt/expect-with-temp [Collection [collection]] +(expect "You don't have permissions to do that." - ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection)) - {:name "My Beautiful Collection", :color "#ABCDEF"})) + (tt/with-temp Collection [collection] + ((user->client :rasta) :put 403 (str "collection/" (u/get-id collection)) + {:name "My Beautiful Collection", :color "#ABCDEF"}))) diff --git a/test/metabase/api/label_test.clj b/test/metabase/api/label_test.clj index b15fc75a6229b5db37d3258082eedb1e53a87d0c..8c7e20fe4a452526e289302802b2c3480ad7936c 100644 --- a/test/metabase/api/label_test.clj +++ b/test/metabase/api/label_test.clj @@ -9,8 +9,8 @@ ;;; GET /api/label -- list all labels (tt/expect-with-temp [Label [{label-1-id :id} {:name "Toucan-Approved"}] - Label [{label-2-id :id} {:name "non-Toucan-Approved"}]] - [{:id label-2-id, :name "non-Toucan-Approved", :slug "non_toucan_approved", :icon nil} ; should be sorted by name, case-insensitive + Label [{label-2-id :id} {:name "non-Toucan-Approved"}]] + [{:id label-2-id, :name "non-Toucan-Approved", :slug "non_toucan_approved", :icon nil} ; should be sorted by name, case-insensitive {:id label-1-id, :name "Toucan-Approved", :slug "toucan_approved", :icon nil}] ((user->client :rasta) :get 200, "label")) @@ -27,7 +27,8 @@ ((user->client :rasta) :put 200, (str "label/" label-id) {:name "Bird-Friendly"})) ;;; DELETE /api/label/:id -- delete a label -(tt/expect-with-temp [Label [{label-id :id} {:name "This will make the toucan very cross!"}]] +(expect nil - (do ((user->client :rasta) :delete 204, (str "label/" label-id)) - (Label label-id))) + (tt/with-temp Label [{label-id :id} {:name "This will make the toucan very cross!"}] + ((user->client :rasta) :delete 204, (str "label/" label-id)) + (Label label-id))) diff --git a/test/metabase/api/pulse_test.clj b/test/metabase/api/pulse_test.clj index ab1feee536aa6b5583e6019f3a95c7b1d93e9042..499787cb67e19f08c799e8b1f00c2950ae4a7d7a 100644 --- a/test/metabase/api/pulse_test.clj +++ b/test/metabase/api/pulse_test.clj @@ -174,9 +174,9 @@ ;; ## DELETE /api/pulse/:id -(tt/expect-with-temp [Pulse [pulse]] +(expect nil - (do + (tt/with-temp Pulse [pulse] ((user->client :rasta) :delete 204 (format "pulse/%d" (:id pulse))) (pulse/retrieve-pulse (:id pulse)))) diff --git a/test/metabase/api/segment_test.clj b/test/metabase/api/segment_test.clj index 59d61adfbab62d377505ef56a83b3cbda14436be..2a2582f4e07b41cefaa32f1e03d82e9b504a6b89 100644 --- a/test/metabase/api/segment_test.clj +++ b/test/metabase/api/segment_test.clj @@ -372,10 +372,11 @@ ;;; PUT /api/segment/id. Can I update a segment's name without specifying `:points_of_interest` and `:show_in_getting_started`? -(tt/expect-with-temp [Segment [segment]] - :ok - (do ((user->client :crowberto) :put 200 (str "segment/" (u/get-id segment)) - {:name "Cool name" - :revision_message "WOW HOW COOL" - :definition {}}) - :ok)) +(expect + (tt/with-temp Segment [segment] + ;; just make sure API call doesn't barf + ((user->client :crowberto) :put 200 (str "segment/" (u/get-id segment)) + {:name "Cool name" + :revision_message "WOW HOW COOL" + :definition {}}) + true)) diff --git a/test/metabase/query_processor_test/filter_test.clj b/test/metabase/query_processor_test/filter_test.clj index 1061d553afb9f392d99fb7b6458051c5f892fd51..404115466401a966c31787b8288213f905f84c58 100644 --- a/test/metabase/query_processor_test/filter_test.clj +++ b/test/metabase/query_processor_test/filter_test.clj @@ -343,3 +343,11 @@ (ql/aggregation (ql/count)) (ql/filter (ql/and (ql/not (ql/> $id 32)) (ql/contains $name "BBQ"))))))) + + +;; make sure that filtering with dates truncating to minutes works (#4632) +(expect-with-non-timeseries-dbs [107] (first-row + (format-rows-by [int] + (data/run-query checkins + (ql/aggregation (ql/count)) + (ql/filter (ql/between (ql/datetime-field $date :minute) "2015-01-01T12:30:00" "2015-05-31")))))) diff --git a/test/metabase/sync_database/analyze_test.clj b/test/metabase/sync_database/analyze_test.clj index 6d829ad069d7216467a1263ab849f530a39ca47a..fe57c4d943a98aa1e62861684171ef3525b9b9fe 100644 --- a/test/metabase/sync_database/analyze_test.clj +++ b/test/metabase/sync_database/analyze_test.clj @@ -1,10 +1,20 @@ (ns metabase.sync-database.analyze-test (:require [clojure.string :as str] [expectations :refer :all] + [metabase + [driver :as driver] + [util :as u]] [metabase.db.metadata-queries :as metadata-queries] + [metabase.models + [field :refer [Field]] + [table :as table :refer [Table]]] [metabase.sync-database.analyze :refer :all] - [metabase.test.util :as tu])) - + [metabase.test + [data :as data] + [util :as tu]] + [metabase.test.data.users :refer :all] + [toucan.db :as db] + [toucan.util.test :as tt])) ;; test:cardinality-and-extract-field-values ;; (#2332) check that if field values are long we skip over them @@ -22,8 +32,8 @@ ;;; ## mark-json-field! -(tu/resolve-private-vars metabase.sync-database.analyze values-are-valid-json?) -(tu/resolve-private-vars metabase.sync-database.analyze values-are-valid-emails?) +(tu/resolve-private-vars metabase.sync-database.analyze + values-are-valid-json? values-are-valid-emails?) (def ^:const ^:private fake-values-seq-json "A sequence of values that should be marked is valid JSON.") @@ -72,3 +82,85 @@ (expect false (values-are-valid-emails? [100])) (expect false (values-are-valid-emails? ["true"])) (expect false (values-are-valid-emails? ["false"])) + +;; Tests to avoid analyzing hidden tables +(defn- unanalyzed-fields-count [table] + (assert (pos? ;; don't let ourselves be fooled if the test passes because the table is + ;; totally broken or has no fields. Make sure we actually test something + (db/count Field :table_id (u/get-id table)))) + (db/count Field :last_analyzed nil, :table_id (u/get-id table))) + +(defn- latest-sync-time [table] + (db/select-one-field :last_analyzed Field + :last_analyzed [:not= nil] + :table_id (u/get-id table) + {:order-by [[:last_analyzed :desc]]})) + +(defn- set-table-visibility-type! [table visibility-type] + ((user->client :crowberto) :put 200 (format "table/%d" (:id table)) {:display_name "hiddentable" + :entity_type "person" + :visibility_type visibility-type + :description "What a nice table!"})) + +(defn- api-sync! [table] + ((user->client :crowberto) :post 200 (format "database/%d/sync" (:db_id table)))) + +(defn- analyze! [table] + (let [db-id (:db_id table)] + (analyze-data-shape-for-tables! (driver/database-id->driver db-id) {:id db-id}))) + +;; expect all the kinds of hidden tables to stay un-analyzed through transitions and repeated syncing +(expect + 1 + (tt/with-temp* [Table [table {:rows 15}] + Field [field {:table_id (:id table)}]] + (set-table-visibility-type! table "hidden") + (api-sync! table) + (set-table-visibility-type! table "cruft") + (set-table-visibility-type! table "cruft") + (api-sync! table) + (set-table-visibility-type! table "technical") + (api-sync! table) + (set-table-visibility-type! table "technical") + (api-sync! table) + (api-sync! table) + (unanalyzed-fields-count table))) + +;; same test not coming through the api +(expect + 1 + (tt/with-temp* [Table [table {:rows 15}] + Field [field {:table_id (:id table)}]] + (set-table-visibility-type! table "hidden") + (analyze! table) + (set-table-visibility-type! table "cruft") + (set-table-visibility-type! table "cruft") + (analyze! table) + (set-table-visibility-type! table "technical") + (analyze! table) + (set-table-visibility-type! table "technical") + (analyze! table) + (analyze! table) + (unanalyzed-fields-count table))) + +;; un-hiding a table should cause it to be analyzed +(expect + 0 + (tt/with-temp* [Table [table {:rows 15}] + Field [field {:table_id (:id table)}]] + (set-table-visibility-type! table "hidden") + (set-table-visibility-type! table nil) + (unanalyzed-fields-count table))) + +;; re-hiding a table should not cause it to be analyzed +(expect + ;; create an initially hidden table + (tt/with-temp* [Table [table {:rows 15, :visibility_type "hidden"}] + Field [field {:table_id (:id table)}]] + ;; switch the table to visible (triggering a sync) and get the last sync time + (let [last-sync-time (do (set-table-visibility-type! table nil) + (latest-sync-time table))] + ;; now make it hidden again + (set-table-visibility-type! table "hidden") + ;; sync time shouldn't change + (= last-sync-time (latest-sync-time table))))) diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj index 09a49d748858a05f77da2fb9f6ead44c2676e365..a02c1905a37d7fed7afc45e0eea83370be57b0c1 100644 --- a/test/metabase/test/data.clj +++ b/test/metabase/test/data.clj @@ -9,6 +9,7 @@ [query-processor :as qp] [sync-database :as sync-database] [util :as u]] + metabase.driver.h2 [metabase.models [database :refer [Database]] [field :as field :refer [Field]] @@ -19,6 +20,7 @@ [metabase.test.data [dataset-definitions :as defs] [datasets :refer [*driver*]] + h2 [interface :as i]] [schema.core :as s] [toucan.db :as db])