summaryrefslogtreecommitdiff
path: root/src/select.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-03-13 10:35:08 -0500
committerJohn Wiegley <johnw@newartisans.com>2012-03-13 10:35:08 -0500
commit37347bad56646f3f3e7e10ee0303e6429c29bbba (patch)
tree5593a508d32ee14de39cbdb1b51e7f1908d21ff2 /src/select.cc
parentc4e942fcb163583e426dc1f65b0afb0bb75c21e3 (diff)
downloadfork-ledger-37347bad56646f3f3e7e10ee0303e6429c29bbba.tar.gz
fork-ledger-37347bad56646f3f3e7e10ee0303e6429c29bbba.tar.bz2
fork-ledger-37347bad56646f3f3e7e10ee0303e6429c29bbba.zip
Add experimental support for select queries
Diffstat (limited to 'src/select.cc')
-rw-r--r--src/select.cc441
1 files changed, 441 insertions, 0 deletions
diff --git a/src/select.cc b/src/select.cc
new file mode 100644
index 00000000..56bd3f2d
--- /dev/null
+++ b/src/select.cc
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2003-2012, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <system.hh>
+
+#include "select.h"
+#include "journal.h"
+#include "account.h"
+#include "report.h"
+#include "output.h"
+#include "print.h"
+#include "chain.h"
+#include "filters.h"
+#include "scope.h"
+#include "op.h"
+
+namespace ledger {
+
+namespace {
+ bool get_principal_identifiers(expr_t::ptr_op_t expr, string& ident,
+ bool do_transforms = false)
+ {
+ bool result = true;
+
+ if (expr->is_ident()) {
+ string name(expr->as_ident());
+ if (name == "date" || name == "aux_date" || name == "payee") {
+ if (! ident.empty() &&
+ ! (name == "date" || name == "aux_date" || name == "payee"))
+ result = false;
+ ident = name;
+ }
+ else if (name == "account") {
+ if (! ident.empty() && ! (name == "account"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_account");
+ }
+ else if (name == "amount") {
+ if (! ident.empty() && ! (name == "amount"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_amount");
+ }
+ else if (name == "total") {
+ if (! ident.empty() && ! (name == "total"))
+ result = false;
+ ident = name;
+ if (do_transforms)
+ expr->set_ident("display_total");
+ }
+ }
+
+ if (expr->kind > expr_t::op_t::TERMINALS || expr->is_scope()) {
+ if (expr->left()) {
+ if (! get_principal_identifiers(expr->left(), ident, do_transforms))
+ result = false;
+ if (expr->kind > expr_t::op_t::UNARY_OPERATORS && expr->has_right())
+ if (! get_principal_identifiers(expr->right(), ident, do_transforms))
+ result = false;
+ }
+ }
+
+ return result;
+ }
+}
+
+value_t select_command(call_scope_t& args)
+{
+ string text = "select " + join_args(args);
+ if (text.empty())
+ throw std::logic_error(_("Usage: select TEXT"));
+
+ report_t& report(find_scope<report_t>(args));
+
+ // Our first step is to divide the select statement into its principal
+ // parts:
+ //
+ // SELECT <VALEXPR-LIST>
+ // FROM <NAME>
+ // WHERE <VALEXPR>
+ // DISPLAY <VALEXPR>
+ // COLLECT <VALEXPR>
+ // GROUP BY <VALEXPR>
+ // STYLE <NAME>
+
+ boost::regex select_re
+ ("(select|from|where|display|collect|group\\s+by|style)\\s+"
+ "(.+?)"
+ "(?=(\\s+(from|where|display|collect|group\\s+by|style)\\s+|$))",
+ boost::regex::perl | boost::regex::icase);
+
+ boost::regex from_accounts_re("from\\s+accounts\\>");
+ bool accounts_report = boost::regex_search(text, from_accounts_re);
+
+ boost::sregex_iterator m1(text.begin(), text.end(), select_re);
+ boost::sregex_iterator m2;
+
+ expr_t::ptr_op_t report_functor;
+ std::ostringstream formatter;
+
+ while (m1 != m2) {
+ const boost::match_results<string::const_iterator>& match(*m1);
+
+ string keyword(match[1]);
+ string arg(match[2]);
+
+ DEBUG("select.parse", "keyword: " << keyword);
+ DEBUG("select.parse", "arg: " << arg);
+
+ if (keyword == "select") {
+ expr_t args_expr(arg);
+ value_t columns(split_cons_expr(args_expr.get_op()));
+ bool first = true;
+ string thus_far = "";
+
+ std::size_t cols = 0;
+ if (report.HANDLED(columns_))
+ cols = lexical_cast<std::size_t>(report.HANDLER(columns_).value);
+ else if (const char * columns_env = std::getenv("COLUMNS"))
+ cols = lexical_cast<std::size_t>(columns_env);
+ else
+ cols = 80;
+
+ std::size_t date_width =
+ (report.HANDLED(date_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(date_width_).str()) :
+ static_cast<std::size_t>
+ (format_date(CURRENT_DATE(),FMT_PRINTED).length()));
+ std::size_t payee_width =
+ (report.HANDLED(payee_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(payee_width_).str()) :
+ std::size_t(double(cols) * 0.263157));
+ std::size_t account_width =
+ (report.HANDLED(account_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(account_width_).str()) :
+ std::size_t(double(cols) * 0.302631));
+ std::size_t amount_width =
+ (report.HANDLED(amount_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(amount_width_).str()) :
+ std::size_t(double(cols) * 0.157894));
+ std::size_t total_width =
+ (report.HANDLED(total_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(total_width_).str()) :
+ amount_width);
+ std::size_t meta_width =
+ (report.HANDLED(meta_width_) ?
+ lexical_cast<std::size_t>(report.HANDLER(meta_width_).str()) :
+ 10);
+
+ bool saw_date = false;
+ bool saw_payee = false;
+ bool saw_account = false;
+ bool saw_amount = false;
+ bool saw_total = false;
+ bool saw_meta = false;
+
+ std::size_t cols_needed = 0;
+ foreach (const value_t& column, columns.to_sequence()) {
+ string ident;
+ if (get_principal_identifiers(as_expr(column), ident)) {
+ if (ident == "date" || ident == "aux_date") {
+ cols_needed += date_width + 1;
+ saw_date = true;
+ }
+ else if (ident == "payee") {
+ cols_needed += payee_width + 1;
+ saw_payee = true;
+ }
+ else if (ident == "account") {
+ cols_needed += account_width + 1;
+ saw_account = true;
+ }
+ else if (ident == "amount") {
+ cols_needed += amount_width + 1;
+ saw_amount = true;
+ }
+ else if (ident == "total") {
+ cols_needed += total_width + 1;
+ saw_total = true;
+ }
+ else {
+ cols_needed += meta_width + 1;
+ saw_meta = true;
+ }
+ }
+ }
+
+ while ((saw_account || saw_payee) && cols_needed < cols) {
+ if (saw_account && cols_needed < cols) {
+ ++account_width;
+ ++cols_needed;
+ if (cols_needed < cols) {
+ ++account_width;
+ ++cols_needed;
+ }
+ }
+ if (saw_payee && cols_needed < cols) {
+ ++payee_width;
+ ++cols_needed;
+ }
+ }
+
+ while ((saw_account || saw_payee) && cols_needed > cols &&
+ account_width > 5 && payee_width > 5) {
+ DEBUG("auto.columns", "adjusting account down");
+ if (saw_account && cols_needed > cols) {
+ --account_width;
+ --cols_needed;
+ if (cols_needed > cols) {
+ --account_width;
+ --cols_needed;
+ }
+ }
+ if (saw_payee && cols_needed > cols) {
+ --payee_width;
+ --cols_needed;
+ }
+ DEBUG("auto.columns", "account_width now = " << account_width);
+ }
+
+ if (! report.HANDLED(date_width_))
+ report.HANDLER(date_width_).value = to_string(date_width);
+ if (! report.HANDLED(payee_width_))
+ report.HANDLER(payee_width_).value = to_string(payee_width);
+ if (! report.HANDLED(account_width_))
+ report.HANDLER(account_width_).value = to_string(account_width);
+ if (! report.HANDLED(amount_width_))
+ report.HANDLER(amount_width_).value = to_string(amount_width);
+ if (! report.HANDLED(total_width_))
+ report.HANDLER(total_width_).value = to_string(total_width);
+
+ foreach (const value_t& column, columns.to_sequence()) {
+ if (first)
+ first = false;
+ else
+ formatter << ' ';
+
+ formatter << "%(";
+
+ string ident;
+ if (get_principal_identifiers(as_expr(column), ident, true)) {
+ if (ident == "date" || ident == "aux_date") {
+ formatter << "ansify_if("
+ << "ansify_if(justify(format_date(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << "), int(date_width)),";
+ formatter << "green if color and date > today),"
+ << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(date_width) + 1";
+ }
+ else if (ident == "payee") {
+ formatter << "ansify_if("
+ << "ansify_if(justify(truncated(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << ", int(payee_width)), int(payee_width)),";
+ formatter << "bold if color and !cleared and actual),"
+ << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(payee_width) + 1";
+ }
+ else if (ident == "account") {
+ formatter << "ansify_if("
+ << "ansify_if(";
+
+ if (accounts_report) {
+ formatter << "partial_account(options.flat), blue if color),";
+ } else {
+ formatter << "justify(truncated(";
+ as_expr(column)->print(formatter);
+ formatter << ", int(account_width), int(abbrev_len)),"
+ << "int(account_width)),";
+ formatter << "true, color),";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "int(account_width) + 1";
+ }
+
+ formatter << " bold if should_bold)";
+ }
+ else if (ident == "amount" || ident == "total") {
+ formatter << "ansify_if("
+ << "justify(scrub(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << "), ";
+
+ if (ident == "amount")
+ formatter << "int(amount_width),";
+ else
+ formatter << "int(total_width),";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+
+ if (ident == "amount")
+ thus_far += "int(amount_width)";
+ else
+ thus_far += "int(total_width)";
+
+ if (thus_far.empty())
+ formatter << "-1";
+ else
+ formatter << thus_far;
+
+ formatter << ", true, color),"
+ << " bold if should_bold)";
+
+ thus_far += " + 1";
+ }
+ else {
+ formatter << "ansify_if("
+ << "justify(truncated(";
+
+ as_expr(column)->print(formatter);
+
+ formatter << ", int(meta_width or 10)), int(meta_width) or 10),";
+ formatter << "bold if should_bold)";
+
+ if (! thus_far.empty())
+ thus_far += " + ";
+ thus_far += "(int(meta_width) or 10) + 1";
+ }
+ }
+ formatter << ")";
+ }
+ formatter << "\\n";
+ }
+ else if (keyword == "from") {
+ DEBUG("select.parse", "formatter: " << formatter.str());
+
+ if (arg == "xacts" || arg == "txns" || arg == "transactions") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new print_xacts(report,
+ report.HANDLED(raw))),
+ report, string("#select")));
+ }
+ else if (arg == "posts" || arg == "postings") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+ else if (arg == "accounts") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<account_t, acct_handler_ptr, &report_t::accounts_report>
+ (acct_handler_ptr(new format_accounts(report, formatter.str())),
+ report, string("#select")));
+ }
+ else if (arg == "commodities") {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<post_t, post_handler_ptr, &report_t::commodities_report>
+ (post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+ }
+ else if (keyword == "where") {
+#if 0
+ query_t query;
+ keep_details_t keeper(true, true, true);
+ expr_t::ptr_op_t expr =
+ query.parse_args(string_value(arg).to_sequence(), keeper, false, true);
+ report.HANDLER(limit_).on("#select", query.get_query(query_t::QUERY_LIMIT));
+#else
+ report.HANDLER(limit_).on("#select", arg);
+#endif
+ }
+ else if (keyword == "display") {
+ report.HANDLER(display_).on("#select", arg);
+ }
+ else if (keyword == "collect") {
+ report.HANDLER(amount_).on("#select", arg);
+ }
+ else if (keyword == "group by") {
+ report.HANDLER(group_by_).on("#select", arg);
+ }
+ else if (keyword == "style") {
+ if (arg == "csv") {
+ }
+ else if (arg == "xml") {
+ }
+ else if (arg == "emacs") {
+ }
+ else if (arg == "org") {
+ }
+ }
+
+ ++m1;
+ }
+
+ if (! report_functor) {
+ report_functor = expr_t::op_t::wrap_functor
+ (reporter<>(post_handler_ptr(new format_posts(report, formatter.str())),
+ report, string("#select")));
+ }
+
+ call_scope_t call_args(report);
+ return report_functor->as_function()(call_args);
+}
+
+} // namespace ledger