From 474d7d17425cbc9cd45cdc497449b7b99f86bc8f Mon Sep 17 00:00:00 2001 From: Yacine Petitprez Date: Sun, 8 Jul 2018 14:31:21 +0200 Subject: [PATCH] Adding some documentation --- src/clear/sql/query/before_query.cr | 11 +++++++++- src/clear/sql/query/change.cr | 8 +++++--- src/clear/sql/query/connection.cr | 3 +++ src/clear/sql/query/cte.cr | 18 +++++++++++++++++ src/clear/sql/query/execute.cr | 14 ++++++++++++- src/clear/sql/query/fetch.cr | 28 +++++++++++++++++++++----- src/clear/sql/query/with_pagination.cr | 18 ++++++++++++----- 7 files changed, 85 insertions(+), 15 deletions(-) diff --git a/src/clear/sql/query/before_query.cr b/src/clear/sql/query/before_query.cr index b7bb498fa..2ac16131a 100644 --- a/src/clear/sql/query/before_query.cr +++ b/src/clear/sql/query/before_query.cr @@ -2,11 +2,20 @@ module Clear::SQL::Query::BeforeQuery macro included @before_query_triggers : Array(-> Void) + # A hook to apply some operation just before the query is executed. + # + # ```crystal + # call = 0 + # req = Clear::SQL.select("1").before_query{ call += 1 } + # 10.times{ req.execute } + # pp call # 10 + # ``` def before_query(&block : -> Void) @before_query_triggers << block end - def trigger_before_query + # :nodoc: + protected def trigger_before_query @before_query_triggers.each { |bq| bq.call } @before_query_triggers.clear end diff --git a/src/clear/sql/query/change.cr b/src/clear/sql/query/change.cr index ef122186a..60379547f 100644 --- a/src/clear/sql/query/change.cr +++ b/src/clear/sql/query/change.cr @@ -1,7 +1,9 @@ module Clear::SQL::Query::Change - # Call back called when the query is changed - # Just here for being reimplemented - # (e.g. by collection for caching purpose) + # This method is called everytime the request has been changed + # By default, this do nothing and return `self`. However, it can be + # reimplemented to change some behavior when the query is changed + # + # (eg. it is by `Clear::Model::Collection`, to discard cache over collection) def change! : self self end diff --git a/src/clear/sql/query/connection.cr b/src/clear/sql/query/connection.cr index 7de92a77e..a9b7bcc44 100644 --- a/src/clear/sql/query/connection.cr +++ b/src/clear/sql/query/connection.cr @@ -1,7 +1,10 @@ module Clear::SQL module Query::Connection + # Connection used by the query. + # Change it using `use_connection` method getter connection_name : String = "default" + # Change the connection used by the query on execution def use_connection(@connection_name : String) self end diff --git a/src/clear/sql/query/cte.cr b/src/clear/sql/query/cte.cr index 65fde4b8f..468774108 100644 --- a/src/clear/sql/query/cte.cr +++ b/src/clear/sql/query/cte.cr @@ -1,12 +1,29 @@ +# Allow usage of Common Table Expressions (CTE) in the query building module Clear::SQL::Query::CTE + # :nodoc: alias CTEAuthorized = Clear::SQL::SelectBuilder | String + getter cte : Hash(String, CTEAuthorized) + # Add a CTE to the query. + # + # ```crystal + # Clear::SQL.select.with_cte("full_year", + # "SELECT DATE(date)" + # "FROM generate_series(NOW() - INTERVAL '1 year', NOW(), '1 day'::interval) date") + # .select("*").from("full_year") + # # WITH full_year AS ( SELECT DATE(date) ... ) SELECT * FROM full_year; + # ``` def with_cte(name, request : CTEAuthorized) cte[name] = request change! end + # Add a CTE to the query. Use NamedTuple convention: + # ```crystal + # Clear::SQL.select.with_cte(cte: "xxx") + # # WITH cte AS xxx SELECT... + # ``` def with_cte(tuple : NamedTuple) tuple.each do |k, v| cte[k.to_s] = v @@ -14,6 +31,7 @@ module Clear::SQL::Query::CTE change! end + # :nodoc: protected def print_ctes if cte.any? {"WITH ", diff --git a/src/clear/sql/query/execute.cr b/src/clear/sql/query/execute.cr index 31b6ddce6..f328c9fa4 100644 --- a/src/clear/sql/query/execute.cr +++ b/src/clear/sql/query/execute.cr @@ -1,7 +1,19 @@ require "db" module Clear::SQL::Query::Execute - def execute(connection_name : String = "default") + # Execute an operation without asking for return + # If an optional `connection_name` parameter is given, this will + # override the connection used. + # + # ```crystal + # # Apply a method from an extension on multiple database + # + # %(default secondary).each do |cnx| + # Clear::SQL.select("pg_shards('xxx')").execute(cnx) + # end + # ``` + def execute(connection_name : String? = nil) + connection_name ||= self.connection_name Clear::SQL.execute(connection_name, to_sql) end end diff --git a/src/clear/sql/query/fetch.cr b/src/clear/sql/query/fetch.cr index c9ecae996..2da2bab93 100644 --- a/src/clear/sql/query/fetch.cr +++ b/src/clear/sql/query/fetch.cr @@ -18,7 +18,9 @@ module Clear::SQL::Query::Fetch rs.close end - # Use a cursor to fetch the data + # Fetch the data using CURSOR. + # This will prevent Clear to load all the data from the database into memory. + # This is useful if you need to retrieve and update a large dataset. def fetch_with_cursor(count = 1_000, &block : Hash(String, ::Clear::SQL::Any) -> Void) trigger_before_query @@ -48,8 +50,7 @@ module Clear::SQL::Query::Fetch end end - # Get a scalar (EG count) - # + # Helpers to fetch a SELECT with only one row and one column return. def scalar(type : T.class) forall T trigger_before_query @@ -60,10 +61,12 @@ module Clear::SQL::Query::Fetch end end + # Return the first line of the query as Hash(String, ::Clear::SQL::Any) def first limit(1).fetch(fetch_all: true) { |x| return x } end + # Return an array with all the rows fetched. def to_a : Array(Hash(String, ::Clear::SQL::Any)) trigger_before_query @@ -80,10 +83,25 @@ module Clear::SQL::Query::Fetch end # Fetch the result set row per row - # `fetch_all` is helpful in transactional environment, so it stores - # the result and close the resultset before strating to dispatch the data + # `fetch_all` optional parameter is helpful in transactional environment, so it stores + # the result and close the resultset before starting to call yield over the data # preventing creation of a new connection if you need to call SQL into the # yielded block. + # + # ```crystal + # # This is wrong: The connection is still busy retrieving the users: + # Clear::SQL.select.from("users").fetch do |u| + # Clear::SQL.select.from("posts").where { u["id"] == posts.id } + # end + # + # # Instead, use `fetch_all` + # # Clear will store the value of the result set in memory + # # before calling the block, and the connection is now ready to handle + # # another query. + # Clear::SQL.select.from("users").fetch(fetch_all:true) do |u| + # Clear::SQL.select.from("posts").where { u["id"] == posts.id } + # end + # ``` def fetch(fetch_all = false, &block : Hash(String, ::Clear::SQL::Any) -> Void) trigger_before_query diff --git a/src/clear/sql/query/with_pagination.cr b/src/clear/sql/query/with_pagination.cr index 39bb1a9f6..e220c901c 100644 --- a/src/clear/sql/query/with_pagination.cr +++ b/src/clear/sql/query/with_pagination.cr @@ -6,23 +6,30 @@ module Clear::SQL::Query::WithPagination property total_entries : Int64? = nil end - # Maybe this goes on the Collection? + # Enter pagination mode. + # This is helpful to manage paginated table. + # Pagination will handle the page progression automatically and update + # `offset` and `limit` parameters by his own. + # + # ```crystal + # page = query.paginate(2, 50) + # ``` def paginate(page : Int32 = DEFAULT_PAGE, per_page : Int32 = DEFAULT_LIMIT) - # Need to clear these values to get total count first clear_limit.clear_offset @total_entries = count - # Calculate proper offset and set limit page = page < 1 ? 1 : page @limit = per_page.to_i64 @offset = (per_page * (page - 1)).to_i64 change! end + # Return the number of items maximum per page. def per_page limit end + # Return the current page def current_page if offset.nil? || limit.nil? 1 @@ -31,6 +38,7 @@ module Clear::SQL::Query::WithPagination end end + # Return the total number of pages. def total_pages if limit.nil? || total_entries.nil? 1 @@ -39,12 +47,12 @@ module Clear::SQL::Query::WithPagination end end - # current_page - 1 or nil if there is no previous page + # Return `current_page - 1` or `nil` if there is no previous page def previous_page current_page > 1 ? (current_page - 1) : nil end - # current_page + 1 or nil if there is no next page + # Return `current_page + 1` or `nil` if there is no next page def next_page current_page < total_pages ? (current_page + 1) : nil end