(function(root, factory) {
    if (typeof module === 'object' && module.exports) {
        module.exports = factory();
    } else {
        root.nearley = factory();
    }
}(this, function() {

    function Rule(name, symbols, postprocess) {
        this.id = ++Rule.highestId;
        this.name = name;
        this.symbols = symbols;        // a list of literal | regex class | nonterminal
        this.postprocess = postprocess;
        return this;
    }
    Rule.highestId = 0;

    Rule.prototype.toString = function(withCursorAt) {
        function stringifySymbolSequence (e) {
            return e.literal ? JSON.stringify(e.literal) :
                   e.type ? '%' + e.type : e.toString();
        }
        var symbolSequence = (typeof withCursorAt === "undefined")
                             ? this.symbols.map(stringifySymbolSequence).join(' ')
                             : (   this.symbols.slice(0, withCursorAt).map(stringifySymbolSequence).join(' ')
                                 + " ● "
                                 + this.symbols.slice(withCursorAt).map(stringifySymbolSequence).join(' ')     );
        return this.name + " → " + symbolSequence;
    }

    // a State is a rule at a position from a given starting point in the input stream (reference)
    function State(rule, dot, reference, wantedBy) {
        this.rule = rule;
        this.dot = dot;
        this.reference = reference;
        this.data = [];
        this.wantedBy = wantedBy;
        this.isComplete = this.dot === rule.symbols.length;
    }

    State.prototype.toString = function() {
        return "{" + this.rule.toString(this.dot) + "}, from: " + (this.reference || 0);
    };

    State.prototype.nextState = function(child) {
        var state = new State(this.rule, this.dot + 1, this.reference, this.wantedBy);
        state.left = this;
        state.right = child;
        if (state.isComplete) {
            state.data = state.build();
        }
        return state;
    };

    State.prototype.build = function() {
        var children = [];
        var node = this;
        do {
            children.push(node.right.data);
            node = node.left;
        } while (node.left);
        children.reverse();
        return children;
    };

    State.prototype.finish = function() {
        if (this.rule.postprocess) {
            this.data = this.rule.postprocess(this.data, this.reference, Parser.fail);
        }
    };

    function Column(grammar, index) {
        this.grammar = grammar;
        this.index = index;
        this.states = [];
        this.wants = {}; // states indexed by the non-terminal they expect
        this.scannable = []; // list of states that expect a token
        this.completed = {}; // states that are nullable
    }

    Column.prototype.process = function(nextColumn) {
        var states = this.states;
        var wants = this.wants;
        var completed = this.completed;

        for (var w = 0; w < states.length; w++) { // nb. we push() during iteration
            var state = states[w];

            if (state.isComplete) {
                state.finish();
                if (state.data !== Parser.fail) {
                    // complete
                    var wantedBy = state.wantedBy;
                    for (var i = wantedBy.length; i--; ) { // this line is hot
                        var left = wantedBy[i];
                        this.complete(left, state);
                    }

                    // special-case nullables
                    if (state.reference === this.index) {
                        // make sure future predictors of this rule get completed.
                        var exp = state.rule.name;
                        (this.completed[exp] = this.completed[exp] || []).push(state);
                    }
                }

            } else {
                // queue scannable states
                var exp = state.rule.symbols[state.dot];
                if (typeof exp !== 'string') {
                    this.scannable.push(state);
                    continue;
                }

                // predict
                if (wants[exp]) {
                    wants[exp].push(state);

                    if (completed.hasOwnProperty(exp)) {
                        var nulls = completed[exp];
                        for (var i = 0; i < nulls.length; i++) {
                            var right = nulls[i];
                            this.complete(state, right);
                        }
                    }
                } else {
                    wants[exp] = [state];
                    this.predict(exp);
                }
            }
        }
    }

    Column.prototype.predict = function(exp) {
        var rules = this.grammar.byName[exp] || [];

        for (var i = 0; i < rules.length; i++) {
            var r = rules[i];
            var wantedBy = this.wants[exp];
            var s = new State(r, 0, this.index, wantedBy);
            this.states.push(s);
        }
    }

    Column.prototype.complete = function(left, right) {
        var copy = left.nextState(right);
        this.states.push(copy);
    }

    function Grammar(rules, start) {
        this.rules = rules;
        this.start = start || this.rules[0].name;
        var byName = this.byName = {};
        this.rules.forEach(function(rule) {
            if (!byName.hasOwnProperty(rule.name)) {
                byName[rule.name] = [];
            }
            byName[rule.name].push(rule);
        });
    }

    // So we can allow passing (rules, start) directly to Parser for backwards compatibility
    Grammar.fromCompiled = function(rules, start) {
        var lexer = rules.Lexer;
        if (rules.ParserStart) {
          start = rules.ParserStart;
          rules = rules.ParserRules;
        }
        var rules = rules.map(function (r) { return (new Rule(r.name, r.symbols, r.postprocess)); });
        var g = new Grammar(rules, start);
        g.lexer = lexer; // nb. storing lexer on Grammar is iffy, but unavoidable
        return g;
    }

    function StreamLexer() {
      this.reset("");
    }

    StreamLexer.prototype.reset = function(data, state) {
        this.buffer = data;
        this.index = 0;
        this.line = state ? state.line : 1;
        this.lastLineBreak = state ? -state.col : 0;
    }

    StreamLexer.prototype.next = function() {
        if (this.index < this.buffer.length) {
            var ch = this.buffer[this.index++];
            if (ch === '\n') {
              this.line += 1;
              this.lastLineBreak = this.index;
            }
            return {value: ch};
        }
    }

    StreamLexer.prototype.save = function() {
      return {
        line: this.line,
        col: this.index - this.lastLineBreak,
      }
    }

    StreamLexer.prototype.formatError = function(token, message) {
        // nb. this gets called after consuming the offending token,
        // so the culprit is index-1
        var buffer = this.buffer;
        if (typeof buffer === 'string') {
            var nextLineBreak = buffer.indexOf('\n', this.index);
            if (nextLineBreak === -1) nextLineBreak = buffer.length;
            var line = buffer.substring(this.lastLineBreak, nextLineBreak)
            var col = this.index - this.lastLineBreak;
            message += " at line " + this.line + " col " + col + ":\n\n";
            message += "  " + line + "\n"
            message += "  " + Array(col).join(" ") + "^"
            return message;
        } else {
            return message + " at index " + (this.index - 1);
        }
    }

    function Parser(rules, start, options) {
        if (rules instanceof Grammar) {
            var grammar = rules;
            var options = start;
        } else {
            var grammar = Grammar.fromCompiled(rules, start);
        }
        this.grammar = grammar;

        // Read options
        this.options = {
            keepHistory: false,
            lexer: grammar.lexer || new StreamLexer,
        };
        for (var key in (options || {})) {
            this.options[key] = options[key];
        }

        // Setup lexer
        this.lexer = this.options.lexer;
        this.lexerState = undefined;

        // Setup a table
        var column = new Column(grammar, 0);
        var table = this.table = [column];

        // I could be expecting anything.
        column.wants[grammar.start] = [];
        column.predict(grammar.start);
        // TODO what if start rule is nullable?
        column.process();
        this.current = 0; // token index
    }

    // create a reserved token for indicating a parse fail
    Parser.fail = {};

    Parser.prototype.feed = function(chunk) {
        var lexer = this.lexer;
        lexer.reset(chunk, this.lexerState);

        var token;
        while (token = lexer.next()) {
            // We add new states to table[current+1]
            var column = this.table[this.current];

            // GC unused states
            if (!this.options.keepHistory) {
                delete this.table[this.current - 1];
            }

            var n = this.current + 1;
            var nextColumn = new Column(this.grammar, n);
            this.table.push(nextColumn);

            // Advance all tokens that expect the symbol
            var literal = token.text !== undefined ? token.text : token.value;
            var value = lexer.constructor === StreamLexer ? token.value : token;
            var scannable = column.scannable;
            for (var w = scannable.length; w--; ) {
                var state = scannable[w];
                var expect = state.rule.symbols[state.dot];
                // Try to consume the token
                // either regex or literal
                if (expect.test ? expect.test(value) :
                    expect.type ? expect.type === token.type
                                : expect.literal === literal) {
                    // Add it
                    var next = state.nextState({data: value, token: token, isToken: true, reference: n - 1});
                    nextColumn.states.push(next);
                }
            }

            // Next, for each of the rules, we either
            // (a) complete it, and try to see if the reference row expected that
            //     rule
            // (b) predict the next nonterminal it expects by adding that
            //     nonterminal's start state
            // To prevent duplication, we also keep track of rules we have already
            // added

            nextColumn.process();

            // If needed, throw an error:
            if (nextColumn.states.length === 0) {
                // No states at all! This is not good.
                var message = this.lexer.formatError(token, "invalid syntax") + "\n";
                message += "Unexpected " + (token.type ? token.type + " token: " : "");
                message += JSON.stringify(token.value !== undefined ? token.value : token) + "\n";
                var err = new Error(message);
                err.offset = this.current;
                err.token = token;
                throw err;
            }

            // maybe save lexer state
            if (this.options.keepHistory) {
              column.lexerState = lexer.save()
            }

            this.current++;
        }
        if (column) {
          this.lexerState = lexer.save()
        }

        // Incrementally keep track of results
        this.results = this.finish();

        // Allow chaining, for whatever it's worth
        return this;
    };

    Parser.prototype.save = function() {
        var column = this.table[this.current];
        column.lexerState = this.lexerState;
        return column;
    };

    Parser.prototype.restore = function(column) {
        var index = column.index;
        this.current = index;
        this.table[index] = column;
        this.table.splice(index + 1);
        this.lexerState = column.lexerState;

        // Incrementally keep track of results
        this.results = this.finish();
    };

    // nb. deprecated: use save/restore instead!
    Parser.prototype.rewind = function(index) {
        if (!this.options.keepHistory) {
            throw new Error('set option `keepHistory` to enable rewinding')
        }
        // nb. recall column (table) indicies fall between token indicies.
        //        col 0   --   token 0   --   col 1
        this.restore(this.table[index]);
    };

    Parser.prototype.finish = function() {
        // Return the possible parsings
        var considerations = [];
        var start = this.grammar.start;
        var column = this.table[this.table.length - 1]
        column.states.forEach(function (t) {
            if (t.rule.name === start
                    && t.dot === t.rule.symbols.length
                    && t.reference === 0
                    && t.data !== Parser.fail) {
                considerations.push(t);
            }
        });
        return considerations.map(function(c) {return c.data; });
    };

    return {
        Parser: Parser,
        Grammar: Grammar,
        Rule: Rule,
    };

}));


// This is an example of how to use a nearley-made grammar.
var PROMPT = "> ";
var nearley = require("../../lib/nearley.js");
var grammar = require("./grammar.js");

// This is where the action is.
function runmath(s) {
    var ans;
    try {// We want to catch parse errors and die appropriately

        // Make a parser and feed the input
        ans = new nearley.Parser(grammar.ParserRules, grammar.ParserStart)
            .feed(s);

        // Check if there are any results
        if (ans.results.length) {
            return ans.results[0].toString();
        } else {
            // This means the input is incomplete.
            var out = "Error: incomplete input, parse failed. :(";
            return out;
        }
    } catch(e) {
        // Panic in style, by graphically pointing out the error location.
        var out = new Array(PROMPT.length + e.offset + 1).join("-") + "^  Error.";
        //                                  --------
        //                                         ^ This comes from nearley!
        return out;
    }
}

// node readline gunk. Nothing too exciting.
var readline = require('readline'),
    rl = readline.createInterface(process.stdin, process.stdout);

rl.setPrompt(PROMPT);
rl.prompt();

rl.on('line', function(line) {
    console.log(runmath(line));
    rl.prompt();
}).on('close', function() {
    console.log('\nBye.');
    process.exit(0);
});


'use strict';

module.exports = Pbf;

var ieee754 = require('ieee754');

function Pbf(buf) {
    this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
    this.pos = 0;
    this.type = 0;
    this.length = this.buf.length;
}

Pbf.Varint  = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
Pbf.Bytes   = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32

var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
    SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;

Pbf.prototype = {

    destroy: function() {
        this.buf = null;
    },

    // === READING =================================================================

    readFields: function(readField, result, end) {
        end = end || this.length;

        while (this.pos < end) {
            var val = this.readVarint(),
                tag = val >> 3,
                startPos = this.pos;

            this.type = val & 0x7;
            readField(tag, result, this);

            if (this.pos === startPos) this.skip(val);
        }
        return result;
    },

    readMessage: function(readField, result) {
        return this.readFields(readField, result, this.readVarint() + this.pos);
    },

    readFixed32: function() {
        var val = readUInt32(this.buf, this.pos);
        this.pos += 4;
        return val;
    },

    readSFixed32: function() {
        var val = readInt32(this.buf, this.pos);
        this.pos += 4;
        return val;
    },

    // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)

    readFixed64: function() {
        var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
        this.pos += 8;
        return val;
    },

    readSFixed64: function() {
        var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
        this.pos += 8;
        return val;
    },

    readFloat: function() {
        var val = ieee754.read(this.buf, this.pos, true, 23, 4);
        this.pos += 4;
        return val;
    },

    readDouble: function() {
        var val = ieee754.read(this.buf, this.pos, true, 52, 8);
        this.pos += 8;
        return val;
    },

    readVarint: function(isSigned) {
        var buf = this.buf,
            val, b;

        b = buf[this.pos++]; val  =  b & 0x7f;        if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 7;  if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
        b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;
        b = buf[this.pos];   val |= (b & 0x0f) << 28;

        return readVarintRemainder(val, isSigned, this);
    },

    readVarint64: function() { // for compatibility with v2.0.1
        return this.readVarint(true);
    },

    readSVarint: function() {
        var num = this.readVarint();
        return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
    },

    readBoolean: function() {
        return Boolean(this.readVarint());
    },

    readString: function() {
        var end = this.readVarint() + this.pos,
            str = readUtf8(this.buf, this.pos, end);
        this.pos = end;
        return str;
    },

    readBytes: function() {
        var end = this.readVarint() + this.pos,
            buffer = this.buf.subarray(this.pos, end);
        this.pos = end;
        return buffer;
    },

    // verbose for performance reasons; doesn't affect gzipped size

    readPackedVarint: function(arr, isSigned) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readVarint(isSigned));
        return arr;
    },
    readPackedSVarint: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readSVarint());
        return arr;
    },
    readPackedBoolean: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readBoolean());
        return arr;
    },
    readPackedFloat: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readFloat());
        return arr;
    },
    readPackedDouble: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readDouble());
        return arr;
    },
    readPackedFixed32: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readFixed32());
        return arr;
    },
    readPackedSFixed32: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readSFixed32());
        return arr;
    },
    readPackedFixed64: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readFixed64());
        return arr;
    },
    readPackedSFixed64: function(arr) {
        if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
        var end = readPackedEnd(this);
        arr = arr || [];
        while (this.pos < end) arr.push(this.readSFixed64());
        return arr;
    },

    skip: function(val) {
        var type = val & 0x7;
        if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
        else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
        else if (type === Pbf.Fixed32) this.pos += 4;
        else if (type === Pbf.Fixed64) this.pos += 8;
        else throw new Error('Unimplemented type: ' + type);
    },

    // === WRITING =================================================================

    writeTag: function(tag, type) {
        this.writeVarint((tag << 3) | type);
    },

    realloc: function(min) {
        var length = this.length || 16;

        while (length < this.pos + min) length *= 2;

        if (length !== this.length) {
            var buf = new Uint8Array(length);
            buf.set(this.buf);
            this.buf = buf;
            this.length = length;
        }
    },

    finish: function() {
        this.length = this.pos;
        this.pos = 0;
        return this.buf.subarray(0, this.length);
    },

    writeFixed32: function(val) {
        this.realloc(4);
        writeInt32(this.buf, val, this.pos);
        this.pos += 4;
    },

    writeSFixed32: function(val) {
        this.realloc(4);
        writeInt32(this.buf, val, this.pos);
        this.pos += 4;
    },

    writeFixed64: function(val) {
        this.realloc(8);
        writeInt32(this.buf, val & -1, this.pos);
        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
        this.pos += 8;
    },

    writeSFixed64: function(val) {
        this.realloc(8);
        writeInt32(this.buf, val & -1, this.pos);
        writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
        this.pos += 8;
    },

    writeVarint: function(val) {
        val = +val || 0;

        if (val > 0xfffffff || val < 0) {
            writeBigVarint(val, this);
            return;
        }

        this.realloc(4);

        this.buf[this.pos++] =           val & 0x7f  | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
        this.buf[this.pos++] =   (val >>> 7) & 0x7f;
    },

    writeSVarint: function(val) {
        this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
    },

    writeBoolean: function(val) {
        this.writeVarint(Boolean(val));
    },

    writeString: function(str) {
        str = String(str);
        this.realloc(str.length * 4);

        this.pos++; // reserve 1 byte for short string length

        var startPos = this.pos;
        // write the string directly to the buffer and see how much was written
        this.pos = writeUtf8(this.buf, str, this.pos);
        var len = this.pos - startPos;

        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);

        // finally, write the message length in the reserved place and restore the position
        this.pos = startPos - 1;
        this.writeVarint(len);
        this.pos += len;
    },

    writeFloat: function(val) {
        this.realloc(4);
        ieee754.write(this.buf, val, this.pos, true, 23, 4);
        this.pos += 4;
    },

    writeDouble: function(val) {
        this.realloc(8);
        ieee754.write(this.buf, val, this.pos, true, 52, 8);
        this.pos += 8;
    },

    writeBytes: function(buffer) {
        var len = buffer.length;
        this.writeVarint(len);
        this.realloc(len);
        for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
    },

    writeRawMessage: function(fn, obj) {
        this.pos++; // reserve 1 byte for short message length

        // write the message directly to the buffer and see how much was written
        var startPos = this.pos;
        fn(obj, this);
        var len = this.pos - startPos;

        if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);

        // finally, write the message length in the reserved place and restore the position
        this.pos = startPos - 1;
        this.writeVarint(len);
        this.pos += len;
    },

    writeMessage: function(tag, fn, obj) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeRawMessage(fn, obj);
    },

    writePackedVarint:   function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr);   },
    writePackedSVarint:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr);  },
    writePackedBoolean:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr);  },
    writePackedFloat:    function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr);    },
    writePackedDouble:   function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr);   },
    writePackedFixed32:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr);  },
    writePackedSFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); },
    writePackedFixed64:  function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr);  },
    writePackedSFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); },

    writeBytesField: function(tag, buffer) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeBytes(buffer);
    },
    writeFixed32Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeFixed32(val);
    },
    writeSFixed32Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeSFixed32(val);
    },
    writeFixed64Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeFixed64(val);
    },
    writeSFixed64Field: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeSFixed64(val);
    },
    writeVarintField: function(tag, val) {
        this.writeTag(tag, Pbf.Varint);
        this.writeVarint(val);
    },
    writeSVarintField: function(tag, val) {
        this.writeTag(tag, Pbf.Varint);
        this.writeSVarint(val);
    },
    writeStringField: function(tag, str) {
        this.writeTag(tag, Pbf.Bytes);
        this.writeString(str);
    },
    writeFloatField: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed32);
        this.writeFloat(val);
    },
    writeDoubleField: function(tag, val) {
        this.writeTag(tag, Pbf.Fixed64);
        this.writeDouble(val);
    },
    writeBooleanField: function(tag, val) {
        this.writeVarintField(tag, Boolean(val));
    }
};

function readVarintRemainder(l, s, p) {
    var buf = p.buf,
        h, b;

    b = buf[p.pos++]; h  = (b & 0x70) >> 4;  if (b < 0x80) return toNum(l, h, s);
    b = buf[p.pos++]; h |= (b & 0x7f) << 3;  if (b < 0x80) return toNum(l, h, s);
    b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s);
    b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s);
    b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s);
    b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s);

    throw new Error('Expected varint not more than 10 bytes');
}

function readPackedEnd(pbf) {
    return pbf.type === Pbf.Bytes ?
        pbf.readVarint() + pbf.pos : pbf.pos + 1;
}

function toNum(low, high, isSigned) {
    if (isSigned) {
        return high * 0x100000000 + (low >>> 0);
    }

    return ((high >>> 0) * 0x100000000) + (low >>> 0);
}

function writeBigVarint(val, pbf) {
    var low, high;

    if (val >= 0) {
        low  = (val % 0x100000000) | 0;
        high = (val / 0x100000000) | 0;
    } else {
        low  = ~(-val % 0x100000000);
        high = ~(-val / 0x100000000);

        if (low ^ 0xffffffff) {
            low = (low + 1) | 0;
        } else {
            low = 0;
            high = (high + 1) | 0;
        }
    }

    if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
        throw new Error('Given varint doesn\'t fit into 10 bytes');
    }

    pbf.realloc(10);

    writeBigVarintLow(low, high, pbf);
    writeBigVarintHigh(high, pbf);
}

function writeBigVarintLow(low, high, pbf) {
    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
    pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
    pbf.buf[pbf.pos]   = low & 0x7f;
}

function writeBigVarintHigh(high, pbf) {
    var lsb = (high & 0x07) << 4;

    pbf.buf[pbf.pos++] |= lsb         | ((high >>>= 3) ? 0x80 : 0); if (!high) return;
    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
    pbf.buf[pbf.pos++]  = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
    pbf.buf[pbf.pos++]  = high & 0x7f;
}

function makeRoomForExtraLength(startPos, len, pbf) {
    var extraLen =
        len <= 0x3fff ? 1 :
        len <= 0x1fffff ? 2 :
        len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7));

    // if 1 byte isn't enough for encoding message length, shift the data to the right
    pbf.realloc(extraLen);
    for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
}

function writePackedVarint(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]);   }
function writePackedSVarint(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]);  }
function writePackedFloat(arr, pbf)    { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]);    }
function writePackedDouble(arr, pbf)   { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]);   }
function writePackedBoolean(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]);  }
function writePackedFixed32(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]);  }
function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
function writePackedFixed64(arr, pbf)  { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]);  }
function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }

// Buffer code below from https://github.com/feross/buffer, MIT-licensed

function readUInt32(buf, pos) {
    return ((buf[pos]) |
        (buf[pos + 1] << 8) |
        (buf[pos + 2] << 16)) +
        (buf[pos + 3] * 0x1000000);
}

function writeInt32(buf, val, pos) {
    buf[pos] = val;
    buf[pos + 1] = (val >>> 8);
    buf[pos + 2] = (val >>> 16);
    buf[pos + 3] = (val >>> 24);
}

function readInt32(buf, pos) {
    return ((buf[pos]) |
        (buf[pos + 1] << 8) |
        (buf[pos + 2] << 16)) +
        (buf[pos + 3] << 24);
}

function readUtf8(buf, pos, end) {
    var str = '';
    var i = pos;

    while (i < end) {
        var b0 = buf[i];
        var c = null; // codepoint
        var bytesPerSequence =
            b0 > 0xEF ? 4 :
            b0 > 0xDF ? 3 :
            b0 > 0xBF ? 2 : 1;

        if (i + bytesPerSequence > end) break;

        var b1, b2, b3;

        if (bytesPerSequence === 1) {
            if (b0 < 0x80) {
                c = b0;
            }
        } else if (bytesPerSequence === 2) {
            b1 = buf[i + 1];
            if ((b1 & 0xC0) === 0x80) {
                c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F);
                if (c <= 0x7F) {
                    c = null;
                }
            }
        } else if (bytesPerSequence === 3) {
            b1 = buf[i + 1];
            b2 = buf[i + 2];
            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
                c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F);
                if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) {
                    c = null;
                }
            }
        } else if (bytesPerSequence === 4) {
            b1 = buf[i + 1];
            b2 = buf[i + 2];
            b3 = buf[i + 3];
            if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
                c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F);
                if (c <= 0xFFFF || c >= 0x110000) {
                    c = null;
                }
            }
        }

        if (c === null) {
            c = 0xFFFD;
            bytesPerSequence = 1;

        } else if (c > 0xFFFF) {
            c -= 0x10000;
            str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
            c = 0xDC00 | c & 0x3FF;
        }

        str += String.fromCharCode(c);
        i += bytesPerSequence;
    }

    return str;
}

function writeUtf8(buf, str, pos) {
    for (var i = 0, c, lead; i < str.length; i++) {
        c = str.charCodeAt(i); // code point

        if (c > 0xD7FF && c < 0xE000) {
            if (lead) {
                if (c < 0xDC00) {
                    buf[pos++] = 0xEF;
                    buf[pos++] = 0xBF;
                    buf[pos++] = 0xBD;
                    lead = c;
                    continue;
                } else {
                    c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
                    lead = null;
                }
            } else {
                if (c > 0xDBFF || (i + 1 === str.length)) {
                    buf[pos++] = 0xEF;
                    buf[pos++] = 0xBF;
                    buf[pos++] = 0xBD;
                } else {
                    lead = c;
                }
                continue;
            }
        } else if (lead) {
            buf[pos++] = 0xEF;
            buf[pos++] = 0xBF;
            buf[pos++] = 0xBD;
            lead = null;
        }

        if (c < 0x80) {
            buf[pos++] = c;
        } else {
            if (c < 0x800) {
                buf[pos++] = c >> 0x6 | 0xC0;
            } else {
                if (c < 0x10000) {
                    buf[pos++] = c >> 0xC | 0xE0;
                } else {
                    buf[pos++] = c >> 0x12 | 0xF0;
                    buf[pos++] = c >> 0xC & 0x3F | 0x80;
                }
                buf[pos++] = c >> 0x6 & 0x3F | 0x80;
            }
            buf[pos++] = c & 0x3F | 0x80;
        }
    }
    return pos;
}


import assert from 'assert'
import { filter } from 'lodash'
import idx from 'idx'

import { validateSqlAST, inspect, wrap } from '../util'
import {
  joinPrefix,
  thisIsNotTheEndOfThisBatch,
  whereConditionIsntSupposedToGoInsideSubqueryOrOnNextBatch
} from './shared'

export default async function stringifySqlAST(topNode, context, options) {
  validateSqlAST(topNode)

  let dialect = options.dialectModule

  if (!dialect && options.dialect) {
    dialect = require('./dialects/' + options.dialect)
  }

  // recursively figure out all the selections, joins, and where conditions that we need
  let { selections, tables, wheres, orders } = await _stringifySqlAST(
    null, topNode, [],
    context, [], [],
    [], [], options.batchScope,
    dialect
  )

  // make sure these are unique by converting to a set and then back to an array
  // e.g. we want to get rid of things like `SELECT user.id as id, user.id as id, ...`
  // GraphQL does not prevent queries with duplicate fields
  selections = [ ...new Set(selections) ]

  // bail out if they made no selections
  if (!selections.length) return ''

  // put together the SQL query
  let sql = 'SELECT\n  '
    + selections.join(',\n  ') + '\n'
    + tables.join('\n')

  wheres = filter(wheres)
  if (wheres.length) {
    sql += '\nWHERE ' + wheres.join(' AND ')
  }

  if (orders.length) {
    sql += '\nORDER BY ' + stringifyOuterOrder(orders, dialect.quote)
  }

  return sql
}

async function _stringifySqlAST(parent, node, prefix, context, selections, tables, wheres, orders, batchScope, dialect) {
  const { quote: q } = dialect
  const parentTable = node.fromOtherTable || (parent && parent.as)
  switch (node.type) {
  case 'table':
    await handleTable(parent, node, prefix, context, selections, tables, wheres, orders, batchScope, dialect)

    // recurse thru nodes
    if (thisIsNotTheEndOfThisBatch(node, parent)) {
      for (let child of node.children) {
        await _stringifySqlAST(
          node, child, [ ...prefix, node.as ],
          context, selections, tables,
          wheres, orders, null,
          dialect
        )
      }
    }

    break
  case 'union':
    await handleTable(parent, node, prefix, context, selections, tables, wheres, orders, batchScope, dialect)

    // recurse thru nodes
    if (thisIsNotTheEndOfThisBatch(node, parent)) {
      for (let typeName in node.typedChildren) {
        for (let child of node.typedChildren[typeName]) {
          await _stringifySqlAST(
            node, child, [ ...prefix, node.as ],
            context, selections, tables,
            wheres, orders, null,
            dialect
          )
        }
      }
      for (let child of node.children) {
        await _stringifySqlAST(
          node, child, [ ...prefix, node.as ],
          context, selections, tables,
          wheres, orders, null,
          dialect
        )
      }
    }

    break
  case 'column':
    selections.push(
      `${q(parentTable)}.${q(node.name)} AS ${q(joinPrefix(prefix) + node.as)}`
    )
    break
  case 'columnDeps':
    // grab the dependant columns
    for (let name in node.names) {
      selections.push(
        `${q(parentTable)}.${q(name)} AS ${q(joinPrefix(prefix) + node.names[name])}`
      )
    }
    break
  case 'composite':
    selections.push(
      `${dialect.compositeKey(parentTable, node.name)} AS ${q(joinPrefix(prefix) + node.as)}`
    )
    break
  case 'expression':
    const expr = await node.sqlExpr(`${q(parentTable)}`, node.args || {}, context, node)
    selections.push(
      `${expr} AS ${q(joinPrefix(prefix) + node.as)}`
    )
    break
  case 'noop':
    // we hit this with fields that don't need anything from SQL, they resolve independently
    return
  default:
    throw new Error('unexpected/unknown node type reached: ' + inspect(node))
  }
  return { selections, tables, wheres, orders }
}

async function handleTable(parent, node, prefix, context, selections, tables, wheres, orders, batchScope, dialect) {
  const { quote: q } = dialect
  // generate the "where" condition, if applicable
  if (whereConditionIsntSupposedToGoInsideSubqueryOrOnNextBatch(node, parent)) {
    if (idx(node, _ => _.junction.where)) {
      wheres.push(await node.junction.where(`${q(node.junction.as)}`, node.args || {}, context, node))
    }
    if (node.where) {
      wheres.push(await node.where(`${q(node.as)}`, node.args || {}, context, node))
    }
  }

  if (thisIsNotTheEndOfThisBatch(node, parent)) {
    if (idx(node, _ => _.junction.orderBy)) {
      orders.push({
        table: node.junction.as,
        columns: node.junction.orderBy
      })
    }
    if (node.orderBy) {
      orders.push({
        table: node.as,
        columns: node.orderBy
      })
    }
    if (idx(node, _ => _.junction.sortKey)) {
      orders.push({
        table: node.junction.as,
        columns: sortKeyToOrderColumns(node.junction.sortKey, node.args)
      })
    }
    if (node.sortKey) {
      orders.push({
        table: node.as,
        columns: sortKeyToOrderColumns(node.sortKey, node.args)
      })
    }
  }

  // one-to-many using JOIN
  if (node.sqlJoin) {
    const joinCondition = await node.sqlJoin(`${q(parent.as)}`, q(node.as), node.args || {}, context, node)

    // do we need to paginate? if so this will be a lateral join
    if (node.paginate) {
      await dialect.handleJoinedOneToManyPaginated(parent, node, context, tables, joinCondition)

    // limit has a highly similar approach to paginating
    } else if (node.limit) {
      node.args.first = node.limit
      await dialect.handleJoinedOneToManyPaginated(parent, node, context, tables, joinCondition)
    // otherwite, just a regular left join on the table
    } else {
      tables.push(
        `LEFT JOIN ${node.name} ${q(node.as)} ON ${joinCondition}`
      )
    }

  // many-to-many using batching
  } else if (idx(node, _ => _.junction.sqlBatch)) {
    if (parent) {
      selections.push(
        `${q(parent.as)}.${q(node.junction.sqlBatch.parentKey.name)} AS ${q(joinPrefix(prefix) + node.junction.sqlBatch.parentKey.as)}`
      )
    } else {
      const joinCondition = await node.junction.sqlBatch.sqlJoin(
        `${q(node.junction.as)}`, q(node.as), node.args || {}, context, node
      )
      if (node.paginate) {
        await dialect.handleBatchedManyToManyPaginated(parent, node, context, tables, batchScope, joinCondition)
      } else if (node.limit) {
        node.args.first = node.limit
        await dialect.handleBatchedManyToManyPaginated(parent, node, context, tables, batchScope, joinCondition)
      } else {
        tables.push(
          `FROM ${node.junction.sqlTable} ${q(node.junction.as)}`,
          `LEFT JOIN ${node.name} ${q(node.as)} ON ${joinCondition}`
        )
        // ensures only the correct records are fetched using the value of the parent key
        wheres.push(`${q(node.junction.as)}.${q(node.junction.sqlBatch.thisKey.name)} IN (${batchScope.join(',')})`)
      }
    }

  // many-to-many using JOINs
  } else if (idx(node, _ => _.junction.sqlTable)) {
    const joinCondition1 = await node.junction
      .sqlJoins[0](`${q(parent.as)}`, q(node.junction.as), node.args || {}, context, node)
    const joinCondition2 = await node.junction
      .sqlJoins[1](`${q(node.junction.as)}`, q(node.as), node.args || {}, context, node)

    if (node.paginate) {
      await dialect.handleJoinedManyToManyPaginated(parent, node, context, tables, joinCondition1, joinCondition2)
    } else if (node.limit) {
      node.args.first = node.limit
      await dialect.handleJoinedManyToManyPaginated(parent, node, context, tables, joinCondition1, joinCondition2)
    } else {
      tables.push(
        `LEFT JOIN ${node.junction.sqlTable} ${q(node.junction.as)} ON ${joinCondition1}`
      )
    }
    tables.push(
      `LEFT JOIN ${node.name} ${q(node.as)} ON ${joinCondition2}`
    )

  // one-to-many with batching
  } else if (node.sqlBatch) {
    if (parent) {
      selections.push(
        `${q(parent.as)}.${q(node.sqlBatch.parentKey.name)} AS ${q(joinPrefix(prefix) + node.sqlBatch.parentKey.as)}`
      )
    } else if (node.paginate) {
      await dialect.handleBatchedOneToManyPaginated(parent, node, context, tables, batchScope)
    } else if (node.limit) {
      node.args.first = node.limit
      await dialect.handleBatchedOneToManyPaginated(parent, node, context, tables, batchScope)
      // otherwite, just a regular left join on the table
    } else {
      tables.push(
        `FROM ${node.name} ${q(node.as)}`
      )
      wheres.push(`${q(node.as)}.${q(node.sqlBatch.thisKey.name)} IN (${batchScope.join(',')})`)
    }
  // otherwise, we aren't joining, so we are at the "root", and this is the start of the FROM clause
  } else if (node.paginate) {
    await dialect.handlePaginationAtRoot(parent, node, context, tables)
  } else if (node.limit) {
    node.args.first = node.limit
    await dialect.handlePaginationAtRoot(parent, node, context, tables)
  } else {
    assert(!parent, `Object type for "${node.fieldName}" table must have a "sqlJoin" or "sqlBatch"`)
    tables.push(
      `FROM ${node.name} ${q(node.as)}`
    )
  }
}

// we need one ORDER BY clause on at the very end to make sure everything comes back in the correct order
// ordering inner(sub) queries DOES NOT guarantee the order of those results in the outer query
function stringifyOuterOrder(orders, q) {
  const conditions = []
  for (let condition of orders) {
    for (let column in condition.columns) {
      const direction = condition.columns[column]
      conditions.push(`${q(condition.table)}.${q(column)} ${direction}`)
    }
  }
  return conditions.join(', ')
}

function sortKeyToOrderColumns(sortKey, args) {
  let descending = sortKey.order.toUpperCase() === 'DESC'
  if (args && args.last) {
    descending = !descending
  }
  const orderColumns = {}
  for (let column of wrap(sortKey.key)) {
    orderColumns[column] = descending ? 'DESC' : 'ASC'
  }
  return orderColumns
}


import assert from 'assert'
import { flatMap } from 'lodash'
import deprecate from 'deprecate'
import { getArgumentValues } from 'graphql/execution/values'
import idx from 'idx'

import AliasNamespace from '../alias-namespace'
import { wrap, ensure, unthunk, inspect } from '../util'

class SQLASTNode {
  constructor(parentNode, props) {
    Object.defineProperty(this, 'parent', {
      enumerable: false,
      value: parentNode
    })

    for (let prop in props) {
      this[prop] = props[prop]
    }
  }
}

// an enumeration of all the types that can map to SQL tables
const TABLE_TYPES = [ 'GraphQLObjectType', 'GraphQLUnionType', 'GraphQLInterfaceType' ]

function mergeAll(fieldNodes) {
  const newFieldNodes = [ ...fieldNodes ]
  while (newFieldNodes.length > 1) {
    newFieldNodes.push(merge(newFieldNodes.pop(), newFieldNodes.pop()))
  }
  return newFieldNodes
}

function merge(dest, src) {
  return {
    ...dest,
    selectionSet: {
      ...dest.selectionSet,
      selections: [ ...dest.selectionSet.selections, ...src.selectionSet.selections ]
    }
  }
}

export function queryASTToSqlAST(resolveInfo, options, context) {
  // this is responsible for all the logic regarding creating SQL aliases
  // we need varying degrees of uniqueness and readability
  // force oracle to minify, because it has this 30-character limit on column identifiers
  const namespace = new AliasNamespace(options.dialect === 'oracle' ? true : options.minify)

  // we'll build up the AST representing the SQL recursively
  const sqlAST = {}

  // v0.8 changed the "fieldASTs" property to "fieldNodes". we want to support both
  let fieldNodes = resolveInfo.fieldNodes || resolveInfo.fieldASTs

  // fieldNodes is usually an array of 1 GraphQL node. If a field is requested twice *without* aliases, both nodes will be in this array
  // we need to merge it into one
  fieldNodes = mergeAll(fieldNodes)
  assert.equal(fieldNodes.length, 1, 'We thought this would always have a length of 1. FIX ME!!')

  // this represents the parsed query
  const queryAST = fieldNodes[0]
  // resolveInfo.parentType is from the schema, its the GraphQLObjectType that is parent to the current field
  // this allows us to get the field definition of the current field so we can grab that extra metadata
  // e.g. sqlColumn or sqlJoin, etc.
  const parentType = resolveInfo.parentType
  populateASTNode.call(resolveInfo, queryAST, parentType, sqlAST, namespace, 0, options, context)

  // make sure they started this party on a table, interface or union.
  assert.ok(
    [ 'table', 'union' ].indexOf(sqlAST.type) > -1,
    'Must call joinMonster in a resolver on a field where the type is decorated with "sqlTable".'
  )

  // make sure each "sqlDep" is only specified once at each level. also assign it an alias
  pruneDuplicateSqlDeps(sqlAST, namespace)

  return sqlAST
}

export function populateASTNode(queryASTNode, parentTypeNode, sqlASTNode, namespace, depth, options, context) {
  // first, get the name of the field being queried
  const fieldName = queryASTNode.name.value

  // if this is an internal field (say, for introspection "__typename"), lets ignore it
  if (/^__/.test(fieldName)) {
    sqlASTNode.type = 'noop'
    return
  }

  // then, get the field from the schema definition
  let field = parentTypeNode._fields[fieldName]
  if (!field) {
    throw new Error(`The field "${fieldName}" is not in the ${parentTypeNode.name} type.`)
  }

  let fieldIncludes
  if (idx(sqlASTNode, _ => _.parent.junction.include[fieldName])) {
    fieldIncludes = sqlASTNode.parent.junction.include[fieldName]
    field = {
      ...field,
      ...fieldIncludes
    }
    sqlASTNode.fromOtherTable = sqlASTNode.parent.junction.as
  }

  // allow for explicit ignoring of fields
  if (field.jmIgnoreAll) {
    sqlASTNode.type = 'noop'
    return
  }

  // this flag will keep track of whether multiple rows are needed
  let grabMany = false
  // the actual type might be wrapped in a GraphQLNonNull type
  let gqlType = stripNonNullType(field.type)

  sqlASTNode.args = getArgumentValues(field, queryASTNode, this.variableValues)

  // if list then mark flag true & get the type inside the GraphQLList container type
  if (gqlType.constructor.name === 'GraphQLList') {
    gqlType = stripNonNullType(gqlType.ofType)
    grabMany = true
  }

  // if its a relay connection, there are several things we need to do
  if (gqlType.constructor.name === 'GraphQLObjectType' && gqlType._fields.edges && gqlType._fields.pageInfo) {
    grabMany = true
    // grab the types and fields inside the connection
    const stripped = stripRelayConnection(gqlType, queryASTNode, this.fragments)
    // reassign those
    gqlType = stripNonNullType(stripped.gqlType)
    queryASTNode = stripped.queryASTNode
    // we'll set a flag for pagination.
    if (field.sqlPaginate) {
      sqlASTNode.paginate = true
    }
  } else if (field.sqlPaginate) {
    throw new Error(
      `To paginate the ${gqlType.name} type, it must be a GraphQLObjectType that fulfills the relay spec.
      The type must have a "pageInfo" and "edges" field. https://facebook.github.io/relay/graphql/connections.htm`
    )
  }
  // the typeConfig has all the keyes from the GraphQLObjectType definition
  const config = gqlType._typeConfig

  // is this a table in SQL?
  if (
    !field.jmIgnoreTable
    && TABLE_TYPES.includes(gqlType.constructor.name)
    && config.sqlTable
  ) {
    if (depth >= 1) {
      assert(!field.junctionTable, '"junctionTable" has been replaced with a new API.')
      assert(
        field.sqlJoin || field.sqlBatch || field.junction,
        `If an Object type maps to a SQL table and has a child which is another Object type that also maps to a SQL table,
        you must define "sqlJoin", "sqlBatch", or "junction" on that field to tell joinMonster how to fetch it.
        Or you can ignore it with "jmIgnoreTable". Check the "${fieldName}" field on the "${parentTypeNode.name}" type.`
      )
    }
    handleTable.call(this, sqlASTNode, queryASTNode, field, gqlType, namespace, grabMany, depth, options, context)
  // is this a computed column from a raw expression?
  } else if (field.sqlExpr) {
    sqlASTNode.type = 'expression'
    sqlASTNode.sqlExpr = field.sqlExpr
    let aliasFrom = sqlASTNode.fieldName = field.name
    if (sqlASTNode.defferedFrom) {
      aliasFrom += '@' + parentTypeNode.name
    }
    sqlASTNode.as = namespace.generate('column', aliasFrom)
  // is it just a column? if they specified a sqlColumn or they didn't define a resolver, yeah
  } else if (field.sqlColumn || !field.resolve) {
    sqlASTNode.type = 'column'
    sqlASTNode.name = field.sqlColumn || field.name
    let aliasFrom = sqlASTNode.fieldName = field.name
    if (sqlASTNode.defferedFrom) {
      aliasFrom += '@' + parentTypeNode.name
    }
    sqlASTNode.as = namespace.generate('column', aliasFrom)
  // or maybe it just depends on some SQL columns
  } else if (field.sqlDeps) {
    sqlASTNode.type = 'columnDeps'
    sqlASTNode.names = field.sqlDeps
  // maybe this node wants no business with your SQL, because it has its own resolver
  } else {
    sqlASTNode.type = 'noop'
  }
}

function handleTable(sqlASTNode, queryASTNode, field, gqlType, namespace, grabMany, depth, options, context) {
  const config = gqlType._typeConfig

  sqlASTNode.type = 'table'
  const sqlTable = unthunk(config.sqlTable, sqlASTNode.args || {}, context)
  sqlASTNode.name = sqlTable

  // the graphQL field name will be the default alias for the table
  // if thats taken, this function will just add an underscore to the end to make it unique
  sqlASTNode.as = namespace.generate('table', field.name)

  if (field.orderBy && !sqlASTNode.orderBy) {
    sqlASTNode.orderBy = handleOrderBy(unthunk(field.orderBy, sqlASTNode.args || {}, context))
  }

  // tables have child fields, lets push them to an array
  const children = sqlASTNode.children = sqlASTNode.children || []

  sqlASTNode.fieldName = field.name
  sqlASTNode.grabMany = grabMany

  if (field.where) {
    sqlASTNode.where = field.where
  }

  /*
   * figure out if they are doing one-to-many/many-to-many or join/batch
   * and collect the relevant info
   */

  // are they doing a one-to-many sql join?
  if (field.sqlJoin) {
    sqlASTNode.sqlJoin = field.sqlJoin
  // or a many-to-many?
  } else if (field.junction) {
    const junctionTable = unthunk(ensure(field.junction, 'sqlTable'), sqlASTNode.args || {}, context)
    const junction = sqlASTNode.junction = {
      sqlTable: junctionTable,
      as: namespace.generate('table', junctionTable)
    }
    if (field.junction.include) {
      junction.include = unthunk(field.junction.include, sqlASTNode.args || {}, context)
    }

    if (field.junction.orderBy) {
      junction.orderBy = handleOrderBy(unthunk(field.junction.orderBy, sqlASTNode.args || {}, context))
    }

    if (field.junction.where) {
      junction.where = field.junction.where
    }
    // are they joining or batching?
    if (field.junction.sqlJoins) {
      junction.sqlJoins = field.junction.sqlJoins
    } else if (field.junction.sqlBatch) {
      children.push({
        ...keyToASTChild(ensure(field.junction, 'uniqueKey'), namespace),
        fromOtherTable: junction.as
      })
      junction.sqlBatch = {
        sqlJoin: ensure(field.junction.sqlBatch, 'sqlJoin'),
        thisKey: {
          ...columnToASTChild(ensure(field.junction.sqlBatch, 'thisKey'), namespace),
          fromOtherTable: junction.as
        },
        parentKey: columnToASTChild(ensure(field.junction.sqlBatch, 'parentKey'), namespace)
      }
    } else {
      throw new Error('junction requires either a `sqlJoins` or `sqlBatch`')
    }
  // or are they doing a one-to-many with batching
  } else if (field.sqlBatch) {
    sqlASTNode.sqlBatch = {
      thisKey: columnToASTChild(ensure(field.sqlBatch, 'thisKey'), namespace),
      parentKey: columnToASTChild(ensure(field.sqlBatch, 'parentKey'), namespace)
    }
  }

  if (field.limit) {
    assert(field.orderBy, '`orderBy` is required with `limit`')
    sqlASTNode.limit = unthunk(field.limit, sqlASTNode.args || {}, context)
  }

  if (sqlASTNode.paginate) {
    getSortColumns(field, sqlASTNode, context)
  }

  /*
   * figure out the necessary children. this includes the columns join monster needs, the ones the user needs,
   * and finding out how to map those to the field names
   */

  // the NestHydrationJS library only treats the first column as the unique identifier, therefore we
  // need whichever column that the schema specifies as the unique one to be the first child
  children.push(keyToASTChild(ensure(config, 'uniqueKey'), namespace))

  if (config.alwaysFetch) {
    for (let column of wrap(config.alwaysFetch)) {
      children.push(columnToASTChild(column, namespace))
    }
  }

  // this was created for helping resolve types in union types
  // its been generalized to `alwaysFetch`, as its a useful feature for more than just unions
  if (config.typeHint && [ 'GraphQLUnionType', 'GraphQLInterfaceType' ].includes(gqlType.constructor.name)) {
    deprecate('`typeHint` is deprecated. Use `alwaysFetch` instead.')
    children.push(columnToASTChild(config.typeHint, namespace))
  }

  // go handle the pagination information
  if (sqlASTNode.paginate) {
    handleColumnsRequiredForPagination(sqlASTNode, namespace)
  }

  if (queryASTNode.selectionSet) {
    if (gqlType.constructor.name === 'GraphQLUnionType' || gqlType.constructor.name === 'GraphQLInterfaceType') {
      // union types have special rules for the child fields in join monster
      sqlASTNode.type = 'union'
      sqlASTNode.typedChildren = {}
      handleUnionSelections.call(
        this, sqlASTNode, children,
        queryASTNode.selectionSet.selections, gqlType, namespace,
        depth, options, context
      )
    } else {
      handleSelections.call(
        this, sqlASTNode, children,
        queryASTNode.selectionSet.selections, gqlType, namespace,
        depth, options, context
      )
    }
  }
}

// we need to collect all fields from all the fragments requested in the union type and ask for them in SQL
function handleUnionSelections(
  sqlASTNode,
  children,
  selections,
  gqlType,
  namespace,
  depth,
  options,
  context,
  internalOptions = {}
) {
  for (let selection of selections) {
    // we need to figure out what kind of selection this is
    switch (selection.kind) {
    case 'Field':
      // has this field been requested once already? GraphQL does not protect against duplicates so we have to check for it
      const existingNode = children.find(child => child.fieldName === selection.name.value && child.type === 'table')
      let newNode = new SQLASTNode(sqlASTNode)
      if (existingNode) {
        newNode = existingNode
      } else {
        children.push(newNode)
      }
      if (internalOptions.defferedFrom) {
        newNode.defferedFrom = internalOptions.defferedFrom
      }
      populateASTNode.call(this, selection, gqlType, newNode, namespace, depth + 1, options, context)
      break
    // if its an inline fragment, it has some fields and we gotta recurse thru all them
    case 'InlineFragment':
      {
        const selectionNameOfType = selection.typeCondition.name.value
        // normally, we would scan for the extra join-monster data on the current gqlType.
        // but the gqlType is the Union. The data isn't there, its on each of the types that make up the union
        // lets find that type and handle the selections based on THAT type instead
        const deferredType = this.schema._typeMap[selectionNameOfType]
        const deferToObjectType = deferredType.constructor.name === 'GraphQLObjectType'
        const handler = deferToObjectType ? handleSelections : handleUnionSelections
        if (deferToObjectType) {
          const typedChildren = sqlASTNode.typedChildren
          children = typedChildren[deferredType.name] = typedChildren[deferredType.name] || []
          internalOptions.defferedFrom = gqlType
        }
        handler.call(
          this, sqlASTNode, children,
          selection.selectionSet.selections, deferredType, namespace,
          depth, options, context,
          internalOptions
        )
      }
      break
    // if its a named fragment, we need to grab the fragment definition by its name and recurse over those fields
    case 'FragmentSpread':
      {
        const fragmentName = selection.name.value
        const fragment = this.fragments[fragmentName]
        const fragmentNameOfType = fragment.typeCondition.name.value
        const deferredType = this.schema._typeMap[fragmentNameOfType]
        const deferToObjectType = deferredType.constructor.name === 'GraphQLObjectType'
        const handler = deferToObjectType ? handleSelections : handleUnionSelections
        if (deferToObjectType) {
          const typedChildren = sqlASTNode.typedChildren
          children = typedChildren[deferredType.name] = typedChildren[deferredType.name] || []
          internalOptions.defferedFrom = gqlType
        }
        handler.call(
          this, sqlASTNode, children,
          fragment.selectionSet.selections, deferredType, namespace,
          depth, options, context,
          internalOptions
        )
      }
      break
    /* istanbul ignore next */
    default:
      throw new Error('Unknown selection kind: ' + selection.kind)
    }
  }
}

// the selections could be several types, recursively handle each type here
function handleSelections(
  sqlASTNode,
  children,
  selections,
  gqlType,
  namespace,
  depth,
  options,
  context,
  internalOptions = {},
) {
  for (let selection of selections) {
    // we need to figure out what kind of selection this is
    switch (selection.kind) {
    // if its another field, recurse through that
    case 'Field':
      // has this field been requested once already? GraphQL does not protect against duplicates so we have to check for it
      const existingNode = children.find(child => child.fieldName === selection.name.value && child.type === 'table')
      let newNode = new SQLASTNode(sqlASTNode)
      if (existingNode) {
        newNode = existingNode
      } else {
        children.push(newNode)
      }
      if (internalOptions.defferedFrom) {
        newNode.defferedFrom = internalOptions.defferedFrom
      }
      populateASTNode.call(this, selection, gqlType, newNode, namespace, depth + 1, options, context)
      break
    // if its an inline fragment, it has some fields and we gotta recurse thru all them
    case 'InlineFragment':
      {
        // check to make sure the type of this fragment (or one of the interfaces it implements) matches the type being queried
        const selectionNameOfType = selection.typeCondition.name.value
        const sameType = selectionNameOfType === gqlType.name
        const interfaceType = (gqlType._interfaces || []).map(iface => iface.name).includes(selectionNameOfType)
        if (sameType || interfaceType) {
          handleSelections.call(
            this, sqlASTNode, children,
            selection.selectionSet.selections, gqlType, namespace,
            depth, options, context,
            internalOptions
          )
        }
      }
      break
    // if its a named fragment, we need to grab the fragment definition by its name and recurse over those fields
    case 'FragmentSpread':
      {
        const fragmentName = selection.name.value
        const fragment = this.fragments[fragmentName]
        // make sure fragment type (or one of the interfaces it implements) matches the type being queried
        const fragmentNameOfType = fragment.typeCondition.name.value
        const sameType = fragmentNameOfType === gqlType.name
        const interfaceType = gqlType._interfaces.map(iface => iface.name).indexOf(fragmentNameOfType) >= 0
        if (sameType || interfaceType) {
          handleSelections.call(
            this, sqlASTNode, children,
            fragment.selectionSet.selections, gqlType, namespace,
            depth, options, context,
            internalOptions
          )
        }
      }
      break
    /* istanbul ignore next */
    default:
      throw new Error('Unknown selection kind: ' + selection.kind)
    }
  }
}

// tell the AST we need a column that perhaps the user didnt ask for, but may be necessary for join monster to ID
// objects or associate ones across batches
function columnToASTChild(columnName, namespace) {
  return {
    type: 'column',
    name: columnName,
    fieldName: columnName,
    as: namespace.generate('column', columnName)
  }
}

// generate a name for a composite key based on the individual column names smashed together
// slice them to help prevent exceeding oracle's 30-char identifier limit
function toClumsyName(keyArr) {
  return keyArr.map(name => name.slice(0, 3)).join('#')
}

// keys are necessary for deduplication during the hydration process
// this will handle singular or composite keys
function keyToASTChild(key, namespace) {
  if (typeof key === 'string') {
    return columnToASTChild(key, namespace)
  }
  if (Array.isArray(key)) {
    const clumsyName = toClumsyName(key)
    return {
      type: 'composite',
      name: key,
      fieldName: clumsyName,
      as: namespace.generate('column', clumsyName)
    }
  }
}

function handleColumnsRequiredForPagination(sqlASTNode, namespace) {
  if (sqlASTNode.sortKey || idx(sqlASTNode, _ => _.junction.sortKey)) {
    const sortKey = sqlASTNode.sortKey || sqlASTNode.junction.sortKey
    assert(sortKey.order, '"sortKey" must have "order"')
    // this type of paging uses the "sort key(s)". we need to get this in order to generate the cursor
    for (let column of wrap(ensure(sortKey, 'key'))) {
      const newChild = columnToASTChild(column, namespace)
      // if this joining on a "through-table", the sort key is on the threw table instead of this node's parent table
      if (!sqlASTNode.sortKey) {
        newChild.fromOtherTable = sqlASTNode.junction.as
      }
      sqlASTNode.children.push(newChild)
    }
  } else if (sqlASTNode.orderBy || idx(sqlASTNode, _ => _.junction.orderBy)) {
    // this type of paging can visit arbitrary pages, so lets provide the total number of items
    // on this special "$total" column which we will compute in the query
    const newChild = columnToASTChild('$total', namespace)
    if (sqlASTNode.junction) {
      newChild.fromOtherTable = sqlASTNode.junction.as
    }
    sqlASTNode.children.push(newChild)
  }
}

// if its a connection type, we need to look up the Node type inside their to find the relevant SQL info
function stripRelayConnection(gqlType, queryASTNode, fragments) {
  // get the GraphQL Type inside the list of edges inside the Node from the schema definition
  const edgeType = stripNonNullType(gqlType._fields.edges.type)
  const strippedType = stripNonNullType(stripNonNullType(edgeType.ofType)._fields.node.type)
  // let's remember those arguments on the connection
  const args = queryASTNode.arguments
  // and then find the fields being selected on the underlying type, also buried within edges and Node
  const edges = spreadFragments(queryASTNode.selectionSet.selections, fragments, gqlType.name)
    .find(selection => selection.name.value === 'edges')
  if (edges) {
    queryASTNode = spreadFragments(edges.selectionSet.selections, fragments, gqlType.name)
      .find(selection => selection.name.value === 'node') || {}
  } else {
    queryASTNode = {}
  }
  // place the arguments on this inner field, so our SQL AST picks it up later
  queryASTNode.arguments = args
  return { gqlType: strippedType, queryASTNode }
}

function stripNonNullType(type) {
  return type.constructor.name === 'GraphQLNonNull' ? type.ofType : type
}

// go through and make sure se only ask for each sqlDep once per table
export function pruneDuplicateSqlDeps(sqlAST, namespace) {
  const childrenToLoopOver = []
  if (sqlAST.children) {
    childrenToLoopOver.push(sqlAST.children)
  }
  if (sqlAST.typedChildren) {
    childrenToLoopOver.push(...Object.values(sqlAST.typedChildren))
  }

  for (let children of childrenToLoopOver) {
    // keep track of all the dependent columns at this depth in a Set
    // use one Set per table. usually the table is the same. but sometimes they are pulling in data from
    // a junction table.
    const depsByTable = {}

    // loop thru each child which has "columnDeps", remove it from the tree, and add it to the set
    for (let i = children.length - 1; i >= 0; i--) {
      const child = children[i]
      if (child.type === 'columnDeps') {
        const keyName = child.fromOtherTable || ''
        child.names.forEach(name => {
          if (!depsByTable[keyName]) {
            depsByTable[keyName] = new Set()
          }
          depsByTable[keyName].add(name)
        })
        children.splice(i, 1)
      // or if its another table, recurse on it
      } else if (child.type === 'table' || child.type === 'union') {
        pruneDuplicateSqlDeps(child, namespace)
      }
    }

    // now that we collected the "columnDeps", add them all to one node
    // the "names" property will put all the column names in an object as keys
    // the values of this object will be the SQL alias
    for (let table in depsByTable) {
      const newNode = new SQLASTNode(sqlAST, {
        type: 'columnDeps',
        names: {},
        fromOtherTable: table || null
      })
      depsByTable[table].forEach(name => {
        newNode.names[name] = namespace.generate('column', name)
      })
      children.push(newNode)
    }
  }
}

function getSortColumns(field, sqlASTNode, context) {
  if (field.sortKey) {
    sqlASTNode.sortKey = unthunk(field.sortKey, sqlASTNode.args || {}, context)
  }
  if (field.orderBy) {
    sqlASTNode.orderBy = handleOrderBy(unthunk(field.orderBy, sqlASTNode.args || {}, context))
  }
  if (field.junction) {
    if (field.junction.sortKey) {
      sqlASTNode.junction.sortKey = unthunk(field.junction.sortKey, sqlASTNode.args || {}, context)
    }
    if (field.junction.orderBy) {
      sqlASTNode.junction.orderBy = handleOrderBy(unthunk(field.junction.orderBy, sqlASTNode.args || {}, context))
    }
  }
  if (!sqlASTNode.sortKey && !sqlASTNode.orderBy) {
    if (sqlASTNode.junction) {
      if (!sqlASTNode.junction.sortKey && !sqlASTNode.junction.orderBy) {
        throw new Error('"sortKey" or "orderBy" required if "sqlPaginate" is true')
      }
    } else {
      throw new Error('"sortKey" or "orderBy" required if "sqlPaginate" is true')
    }
  }
  if (sqlASTNode.sortKey && idx(sqlASTNode, _ => _.junction.sortKey)) {
    throw new Error('"sortKey" must be on junction or main table, not both')
  }
  if (sqlASTNode.orderBy && idx(sqlASTNode, _ => _.junction.orderBy)) {
    throw new Error('"orderBy" must be on junction or main table, not both')
  }
}

// instead of fields, selections can be fragments, which is another group of selections
// fragments can be arbitrarily nested
// this function recurses through and gets the relevant fields
function spreadFragments(selections, fragments, typeName) {
  return flatMap(selections, selection => {
    switch (selection.kind) {
    case 'FragmentSpread':
      const fragmentName = selection.name.value
      const fragment = fragments[fragmentName]
      return spreadFragments(fragment.selectionSet.selections, fragments, typeName)
    case 'InlineFragment':
      if (selection.typeCondition.name.value === typeName) {
        return spreadFragments(selection.selectionSet.selections, fragments, typeName)
      }
      return []

    default:
      return selection
    }
  })
}

export function handleOrderBy(orderBy) {
  if (!orderBy) return undefined
  const orderColumns = {}
  if (typeof orderBy === 'object') {
    for (let column in orderBy) {
      let direction = orderBy[column].toUpperCase()
      if (direction !== 'ASC' && direction !== 'DESC') {
        throw new Error(direction + ' is not a valid sorting direction')
      }
      orderColumns[column] = direction
    }
  } else if (typeof orderBy === 'string') {
    orderColumns[orderBy] = 'ASC'
  } else {
    throw new Error('"orderBy" is invalid type: ' + inspect(orderBy))
  }
  return orderColumns
}


import assert from 'assert'

import * as queryAST from './query-ast-to-sql-ast'
import arrToConnection from './array-to-connection'
import AliasNamespace from './alias-namespace'
import nextBatch from './batch-planner'
import { buildWhereFunction, handleUserDbCall, compileSqlAST } from './util'

/*         _ _ _                _
  ___ __ _| | | |__   __ _  ___| | __
 / __/ _` | | | '_ \ / _` |/ __| |/ /
| (_| (_| | | | |_) | (_| | (__|   <
 \___\__,_|_|_|_.__/ \__,_|\___|_|\_\

     _       __ _       _ _   _
  __| | ___ / _(_)_ __ (_) |_(_) ___  _ __  ___
 / _` |/ _ \ |_| | '_ \| | __| |/ _ \| '_ \/ __|
| (_| |  __/  _| | | | | | |_| | (_) | | | \__ \
 \__,_|\___|_| |_|_| |_|_|\__|_|\___/|_| |_|___/
*/

/**
 * User-defined function that sends a raw SQL query to the databse.
 * @callback dbCall
 * @param {String} sql - The SQL generated by `joinMonster` for the batch fetching. Use it to get the data from your database.
 * @param {Function} [done] - An error-first "done" callback. Only define this parameter if you don't want to return a `Promise`.
 * @returns {Promise.<Array>} The raw data as a flat array of objects. Each object must represent a row from the result set.
 */
/**
 * Function for generating a SQL expression.
 * @callback sqlExpr
 * @param {String} tableAlias - The alias generated for this table. Already double-quoted.
 * @param {Object} args - The GraphQL arguments for this field.
 * @param {Object} context - An Object with arbitrary contextual information.
 * @param {Object} sqlASTNode - Join Monster object that abstractly represents this field. Also includes a reference to its parent node. This is useful, for example, if you need to access the parent field's table alias or GraphQL arguments.
 * @returns {String|Promise.<String>} The RAW expression interpolated into the query to compute the column. Unsafe user input must be scrubbed.
 */
/**
 * Function for generating a `WHERE` condition.
 * @callback where
 * @param {String} tableAlias - The alias generated for this table. Already double-quoted.
 * @param {Object} args - The GraphQL arguments for this field.
 * @param {Object} context - An Object with arbitrary contextual information.
 * @param {Object} sqlASTNode - Join Monster object that abstractly represents this field. Also includes a reference to its parent node. This is useful, for example, if you need to access the parent field's table alias or GraphQL arguments.
 * @returns {String|Promise.<String>} The RAW condition for the `WHERE` clause. Omitted if falsy value returned. Unsafe user input must be scrubbed.
 */
/**
 * Function for generating a `JOIN` condition.
 * @callback sqlJoin
 * @param {String} parentTable - The alias generated for the parent's table. Already double-quoted.
 * @param {String} childTable - The alias for the child's table. Already double-quoted.
 * @param {Object} args - The GraphQL arguments for this field.
 * @param {Object} context - An Object with arbitrary contextual information.
 * @param {Object} sqlASTNode - Join Monster object that abstractly represents this field. Also includes a reference to its parent node. This is useful, for example, if you need to access the parent field's table alias or GraphQL arguments.
 * @returns {String} The RAW condition for the `LEFT JOIN`. Unsafe user input must be scrubbed.
 */
/**
 * Rather than a constant value, its a function to dynamically return the value.
 * @callback thunk
 * @param {Object} args - The GraphQL arguments for this field.
 * @param {Object} context - An Object with arbitrary contextual information.
 */

/* _                _
  | |__   ___  __ _(_)_ __    ___  ___  _   _ _ __ ___ ___
  | '_ \ / _ \/ _` | | '_ \  / __|/ _ \| | | | '__/ __/ _ \
  | |_) |  __/ (_| | | | | | \__ \ (_) | |_| | | | (_|  __/
  |_.__/ \___|\__, |_|_| |_| |___/\___/ \__,_|_|  \___\___|
              |___/
*/

/**
 * Takes the GraphQL resolveInfo and returns a hydrated Object with the data.
 * @param {Object} resolveInfo - Contains the parsed GraphQL query, schema definition, and more. Obtained from the fourth argument to the resolver.
 * @param {Object} context - An arbitrary object that gets passed to the `where` function. Useful for contextual infomation that influeces the  `WHERE` condition, e.g. session, logged in user, localization.
 * @param {dbCall} dbCall - A function that is passed the compiled SQL that calls the database and returns a promise of the data.
 * @param {Object} [options]
 * @param {Boolean} options.minify - Generate minimum-length column names in the results table.
 * @param {String} options.dialect - The dialect of SQL your Database uses. Currently `'pg'`, `'oracle'`, `'mariadb'`, `'mysql'`, and `'sqlite3'` are supported.
 * @param {Object} options.dialectModule - An alternative to options.dialect. You can provide a custom implementation of one of the supported dialects.
 * @returns {Promise.<Object>} The correctly nested data from the database.
 */
async function joinMonster(resolveInfo, context, dbCall, options = {}) {
  // we need to read the query AST and build a new "SQL AST" from which the SQL and
  const sqlAST = queryAST.queryASTToSqlAST(resolveInfo, options, context)
  const { sql, shapeDefinition } = await compileSqlAST(sqlAST, context, options)
  if (!sql) return {}

  // call their function for querying the DB, handle the different cases, do some validation, return a promise of the object
  let data = await handleUserDbCall(dbCall, sql, sqlAST, shapeDefinition)

  // if they are paginating, we'll get back an array which is essentially a "slice" of the whole data.
  // this function goes through the data tree and converts the arrays to Connection Objects
  data = arrToConnection(data, sqlAST)

  // so far we handled the first "batch". up until now, additional batches were ignored
  // this function recursively scanss the sqlAST and runs remaining batches
  await nextBatch(sqlAST, data, dbCall, context, options)

  // check for batch data
  if (Array.isArray(data)) {
    const childrenToCheck = sqlAST.children.filter(child => child.sqlBatch)
    return data.filter(d => {
      for (const child of childrenToCheck) {
        if (d[child.fieldName] == null) {
          return false
        }
      }
      return true
    })
  }

  return data
}

/**
 * A helper for resolving the Node type in Relay.
 * @param {String} typeName - The Name of the GraphQLObjectType
 * @param {Object} resolveInfo - Contains the parsed GraphQL query, schema definition, and more. Obtained from the fourth argument to the resolver.
 * @param {Object} context - An arbitrary object that gets passed to the `where` function. Useful for contextual infomation that influeces the  WHERE condition, e.g. session, logged in user, localization.
 * @param {where|Number|String|Array} condition - A value to determine the `where` function for searching the node. If it's a function, that function will be used as the `where` function. Otherwise, it is assumed to be the value(s) of the `primaryKey`. An array of values is needed for composite primary keys.
 * @param {Function} dbCall - A function that is passed the compiled SQL that calls the database and returns (a promise of) the data.
 * @param {Object} [options] - Same as `joinMonster` function's options.
 * @returns {Promise.<Object>} The correctly nested data from the database. The GraphQL Type is added to the "\_\_type\_\_" property, which is helpful for the `resolveType` function in the `nodeDefinitions` of **graphql-relay-js**.
 */
async function getNode(typeName, resolveInfo, context, condition, dbCall, options = {}) {
  // get the GraphQL type from the schema using the name
  const type = resolveInfo.schema._typeMap[typeName]
  assert(type, `Type "${typeName}" not found in your schema.`)
  assert(type._typeConfig.sqlTable, `joinMonster can't fetch a ${typeName} as a Node unless it has "sqlTable" tagged.`)

  // we need to determine what the WHERE function should be
  let where = buildWhereFunction(type, condition, options)

  // our getGraphQLType expects every requested field to be in the schema definition. "node" isn't a parent of whatever type we're getting, so we'll just wrap that type in an object that LOOKS that same as a hypothetical Node type
  const fakeParentNode = {
    _fields: {
      node: {
        type,
        name: type.name.toLowerCase(),
        where
      }
    }
  }
  const namespace = new AliasNamespace(options.minify)
  const sqlAST = {}
  const fieldNodes = resolveInfo.fieldNodes || resolveInfo.fieldASTs
  // uses the same underlying function as the main `joinMonster`
  queryAST.populateASTNode.call(resolveInfo, fieldNodes[0], fakeParentNode, sqlAST, namespace, 0, options, context)
  queryAST.pruneDuplicateSqlDeps(sqlAST, namespace)
  const { sql, shapeDefinition } = await compileSqlAST(sqlAST, context, options)
  const data = arrToConnection(await handleUserDbCall(dbCall, sql, sqlAST, shapeDefinition), sqlAST)
  await nextBatch(sqlAST, data, dbCall, context, options)
  if (!data) return data
  data.__type__ = type
  return data
}

joinMonster.getNode = getNode

// expose the package version for debugging
joinMonster.version = require('../package.json').version
export default joinMonster


import { uniq, chain, map, groupBy, forIn } from 'lodash'
import arrToConnection from '../array-to-connection'
import { handleUserDbCall, maybeQuote, wrap, compileSqlAST } from '../util'
import idx from 'idx'

async function nextBatch(sqlAST, data, dbCall, context, options) {
  // paginated fields are wrapped in connections. strip those off for the batching
  if (sqlAST.paginate) {
    if (Array.isArray(data)) {
      data = chain(data).flatMap('edges').map('node').value()
    } else {
      data = map(data.edges, 'node')
    }
  }
  if (!data || (Array.isArray(data) && data.length === 0)) {
    return
  }

  const children = sqlAST.children
  Object.values(sqlAST.typedChildren || {}).forEach(typedChildren => children.push(...typedChildren))

  // loop through all the child fields that are tables
  return Promise.all(children.map(childAST => nextBatchChild(childAST, data, dbCall, context, options)))
}

// processes a single child of the batch
async function nextBatchChild(childAST, data, dbCall, context, options) {
  if (childAST.type !== 'table' && childAST.type !== 'union') return

  const fieldName = childAST.fieldName

  // see if any begin a new batch
  if (childAST.sqlBatch || idx(childAST, _ => _.junction.sqlBatch)) {
    let thisKey
    let parentKey
    if (childAST.sqlBatch) {
      // if so, we know we'll need to get the key for matching with the parent key
      childAST.children.push(childAST.sqlBatch.thisKey)
      thisKey = childAST.sqlBatch.thisKey.fieldName
      parentKey = childAST.sqlBatch.parentKey.fieldName
    } else if (idx(childAST, _ => _.junction.sqlBatch)) {
      childAST.children.push(childAST.junction.sqlBatch.thisKey)
      thisKey = childAST.junction.sqlBatch.thisKey.fieldName
      parentKey = childAST.junction.sqlBatch.parentKey.fieldName
    }

    if (Array.isArray(data)) {
      // the "batch scope" is teh set of values to match this key against from the previous batch
      const batchScope = uniq(data.map(obj => maybeQuote(obj[parentKey])))
      // generate the SQL, with the batch scope values incorporated in a WHERE IN clause
      const { sql, shapeDefinition } = await compileSqlAST(childAST, context, { ...options, batchScope })
      // grab the data
      let newData = await handleUserDbCall(dbCall, sql, childAST, wrap(shapeDefinition))
      // group the rows by the key so we can match them with the previous batch
      newData = groupBy(newData, thisKey)
      // but if we paginate, we must convert to connection type first
      if (childAST.paginate) {
        forIn(newData, (group, key, obj) => {
          obj[key] = arrToConnection(group, childAST)
        })
      }
      // if we they want many rows, give them an array
      if (childAST.grabMany) {
        for (let obj of data) {
          obj[fieldName] = newData[obj[parentKey]] || (childAST.paginate ? { total: 0, edges: [] } : [])
        }
      } else {
        let matchedData = []
        for (let obj of data) {
          const ob = newData[obj[parentKey]]
          if (ob) {
            obj[fieldName] = arrToConnection(newData[obj[parentKey]][0], childAST)
            matchedData.push(obj)
          } else {
            obj[fieldName] = null
          }
        }
        data = matchedData
      }

      // move down a level and recurse
      const nextLevelData = chain(data)
        .filter(obj => obj != null)
        .flatMap(obj => obj[fieldName])
        .filter(obj => obj != null)
        .value()
      return nextBatch(childAST, nextLevelData, dbCall, context, options)
    }
    const batchScope = [ maybeQuote(data[parentKey]) ]
    const { sql, shapeDefinition } = await compileSqlAST(childAST, context, { ...options, batchScope })
    let newData = await handleUserDbCall(dbCall, sql, childAST, wrap(shapeDefinition))
    newData = groupBy(newData, thisKey)
    if (childAST.paginate) {
      const targets = newData[data[parentKey]]
      data[fieldName] = arrToConnection(targets, childAST)
    } else if (childAST.grabMany) {
      data[fieldName] = newData[data[parentKey]] || []
    } else {
      const targets = newData[data[parentKey]] || []
      data[fieldName] = targets[0]
    }
    if (data) {
      return nextBatch(childAST, data[fieldName], dbCall, context, options)
    }

  // otherwise, just bypass this and recurse down to the next level
  } else if (Array.isArray(data)) {
    const nextLevelData = chain(data)
      .filter(obj => obj != null)
      .flatMap(obj => obj[fieldName])
      .filter(obj => obj != null)
      .value()
    return nextBatch(childAST, nextLevelData, dbCall, context, options)
  } else if (data) {
    return nextBatch(childAST, data[fieldName], dbCall, context, options)
  }
}

export default nextBatch


import { connectionFromArraySlice, cursorToOffset } from 'graphql-relay'
import { objToCursor, wrap, last } from './util'
import idx from 'idx'

// a function for data manipulation AFTER its nested.
// this is only necessary when using the SQL pagination
// we have to interpret the slice that comes back and generate the Connection Object type
function arrToConnection(data, sqlAST) {
  // use "post-order" tree traversal
  for (let astChild of sqlAST.children || []) {
    if (Array.isArray(data)) {
      for (let dataItem of data) {
        recurseOnObjInData(dataItem, astChild)
      }
    } else if (data) {
      recurseOnObjInData(data, astChild)
    }
  }
  const pageInfo = {
    hasNextPage: false,
    hasPreviousPage: false
  }
  if (!data) {
    if (sqlAST.paginate) {
      return {
        pageInfo,
        edges: []
      }
    }
    return null
  }
  // is cases where pagination was done, take the data and convert to the connection object
  // if any two fields happen to become a reference to the same object (when their `uniqueKey`s are the same),
  // we must prevent the recursive processing from visting the same object twice, because mutating the object the first
  // time changes it everywhere. we'll set the `_paginated` property to true to prevent this
  if (sqlAST.paginate && !data._paginated) {
    if (sqlAST.sortKey || idx(sqlAST, _ => _.junction.sortKey)) {
      if (idx(sqlAST, _ => _.args.first)) {
        // we fetched an extra one in order to determine if there is a next page, if there is one, pop off that extra
        if (data.length > sqlAST.args.first) {
          pageInfo.hasNextPage = true
          data.pop()
        }
      } else if (sqlAST.args && sqlAST.args.last) {
        // if backward paging, do the same, but also reverse it
        if (data.length > sqlAST.args.last) {
          pageInfo.hasPreviousPage = true
          data.pop()
        }
        data.reverse()
      }
      // convert nodes to edges and compute the cursor for each
      // TODO: only compute all the cursor if asked for them
      const sortKey = sqlAST.sortKey || sqlAST.junction.sortKey
      const edges = data.map(obj => {
        const cursor = {}
        const key = sortKey.key
        for (let column of wrap(key)) {
          cursor[column] = obj[column]
        }
        return { cursor: objToCursor(cursor), node: obj }
      })
      if (data.length) {
        pageInfo.startCursor = edges[0].cursor
        pageInfo.endCursor = last(edges).cursor
      }
      return { edges, pageInfo, _paginated: true }
    }
    if (sqlAST.orderBy || (sqlAST.junction && sqlAST.junction.orderBy)) {
      let offset = 0
      if (idx(sqlAST, _ => _.args.after)) {
        offset = cursorToOffset(sqlAST.args.after) + 1
      }
      // $total was a special column for determining the total number of items
      const arrayLength = data[0] && parseInt(data[0].$total, 10)
      const connection = connectionFromArraySlice(data, sqlAST.args || {}, { sliceStart: offset, arrayLength })
      connection.total = arrayLength || 0
      connection._paginated = true
      return connection
    }
  }
  return data
}

export default arrToConnection

function recurseOnObjInData(dataObj, astChild) {
  const dataChild = dataObj[astChild.fieldName]
  if (dataChild) {
    dataObj[astChild.fieldName] = arrToConnection(dataObj[astChild.fieldName], astChild)
  }
}


import util from 'util'
import assert from 'assert'
import { nest } from '@stem/nesthydrationjs'
import stringifySQL from './stringifiers/dispatcher'
import resolveUnions from './resolve-unions'
import deprecate from 'deprecate'
const debug = require('debug')('join-monster')

import defineObjectShape from './define-object-shape'

export function emphasize(str, colorCode = 33) {
  return `\n\x1b[1;${colorCode}m${str}\x1b[0m\n`
}

export function inspect(obj, options = {}) {
  return util.inspect(obj, { depth: 12, ...options })
}

// really? yes, really
export function last(arr) {
  return arr[arr.length - 1]
}

export function wrap(maybeArr) {
  if (maybeArr.constructor === Array) {
    return maybeArr
  }
  return [ maybeArr ]
}

export function isEmptyArray(val) {
  return Array.isArray(val) && val.length === 0
}

export function ensure(obj, prop, name) {
  if (!obj[prop]) {
    throw new Error(`property "${prop}" must be defined on object: ${name || util.inspect(obj)}`)
  }
  return obj[prop]
}

export function unthunk(val, ...args) {
  return typeof val === 'function' ? val(...args) : val
}

export function validateSqlAST(topNode) {
  // TODO: this could be a bit more comprehensive
  assert(topNode.sqlJoin == null, 'root level field can not have "sqlJoin"')
}

export function objToCursor(obj) {
  const str = JSON.stringify(obj)
  return Buffer.from(str).toString('base64')
}

export function cursorToObj(cursor) {
  const str = Buffer.from(cursor, 'base64').toString()
  return JSON.parse(str)
}

// wrap in a pair of single quotes for the SQL if needed
export function maybeQuote(value, dialectName) {
  if (value == null) {
    return 'NULL'
  }

  if (typeof value === 'number') return value
  if (value && typeof value.toSQL === 'function') return value.toSQL()
  if (value instanceof Buffer
    && typeof value === 'object'
    && typeof value.toString === 'function') {
    return `X'${value.toString('hex')}'`
  }
  if (dialectName === 'oracle' && value.match(/\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(.\d+)?Z?/)) {
    return value.replace(
      /(\d{4}-\d\d-\d\d)T(\d\d:\d\d:\d\d)(.\d+)?Z?/,
      'TIMESTAMP \'$1 $2$3 UTC\''
    )
  }

  // Picked from https://github.com/brianc/node-postgres/blob/876018/lib/client.js#L235..L260
  // Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c
  let hasBackslash = false
  let escaped = '\''

  for (let i = 0; i < value.length; i++) {
    let c = value[i]
    if (c === '\'') {
      escaped += c + c
    } else if (c === '\\') {
      escaped += c + c
      hasBackslash = true
    } else {
      escaped += c
    }
  }

  escaped += '\''

  if (hasBackslash === true) {
    escaped = ' E' + escaped
  }

  return escaped
}

function getDialectName(options) {
  if (options.dialectModule) {
    return options.dialectModule.name
  }
  return options.dialect || 'sqlite3'
}

export function buildWhereFunction(type, condition, options) {
  const name = getDialectName(options)
  if (typeof condition === 'function') {
    return condition
  // otherwise, we'll assume they gave us the value(s) of the unique key.
  }
  // determine the type of quotes necessary to escape the uniqueKey column
  const quote = [ 'mysql', 'mysql8', 'mariadb' ].includes(name) ? '`' : '"'

  // determine the unique key so we know what to search by
  const uniqueKey = type._typeConfig.uniqueKey

  // handle composite keys
  if (Array.isArray(uniqueKey)) {
    // it must have a corresponding array of values
    assert.equal(
      condition.length,
      uniqueKey.length,
      `The unique key for the "${type.name}" type is a composite. You must provide an array of values for each column.`
    )
    return table => uniqueKey.map((key, i) => `${table}.${quote}${key}${quote} = ${maybeQuote(condition[i])}`).join(' AND ')
    // single keys are simple
  }
  return table => `${table}.${quote}${uniqueKey}${quote} = ${maybeQuote(condition)}`
}

// handles the different callback signatures and return values.
export function handleUserDbCall(dbCall, sql, sqlAST, shapeDefinition) {
  // if there are two args, we're in "callback mode"
  if (dbCall.length === 2) {
    // wrap it in a promise
    return new Promise((resolve, reject) => {
      // wait for them to call "done"
      dbCall(sql, (err, rows) => {
        if (err) {
          reject(err)
        } else {
          rows = validate(rows)
          if (debug.enabled) {
            debug(emphasize('RAW_DATA'), inspect(rows.slice(0, 8)))
            debug(`${rows.length} rows...`)
          }
          const data = nest(rows, shapeDefinition)
          resolveUnions(data, sqlAST)
          if (debug.enabled) {
            debug(emphasize('SHAPED_DATA', inspect(data)))
          }
          resolve(data)
        }
      })
    })
  }

  // otherwise, we are expecting a promise of the data
  const result = dbCall(sql)
  if (typeof result.then === 'function') {
    return result.then(rows => {
      rows = validate(rows)
      if (debug.enabled) {
        debug(emphasize('RAW DATA'), inspect(rows.slice(0, 8)))
        debug(`${rows.length} rows...`)
      }
      // hydrate the data
      // take that shape definition we produced and pass it to the NestHydrationJS library
      const data = nest(rows, shapeDefinition)
      resolveUnions(data, sqlAST)
      if (debug.enabled) {
        debug(emphasize('SHAPED_DATA'), inspect(data))
      }
      return data
    })
  }
  throw new Error('must return a promise of the data or use the callback')
}

// validate the data they gave us
function validate(rows) {
  // its supposed to be an array of objects
  if (Array.isArray(rows)) return rows
  // a check for the most common error. a lot of ORMs return an object with the desired data on the `rows` property
  if (rows && rows.rows) return rows.rows

  throw new Error(
    `"dbCall" function must return/resolve an array of objects where each object is a row from the result set.
    Instead got ${util.inspect(rows, { depth: 3 })}`
  )
}

export async function compileSqlAST(sqlAST, context, options) {
  if (debug.enabled) {
    debug(emphasize('SQL_AST'), inspect(sqlAST))
  }

  // now convert the "SQL AST" to sql
  options.dialect = options.dialect || 'sqlite3'
  if (options.dialect === 'standard') {
    deprecate(
      'dialect "standard" is deprecated, because there is no true implementation of the SQL standard',
      '"sqlite3" is the default'
    )
    options.dialect = 'sqlite3'
  }
  const sql = await stringifySQL(sqlAST, context, options)
  if (debug.enabled) {
    debug(emphasize('SQL'), sql)
  }

  // figure out the shape of the object and define it so later we can pass it to
  // NestHydration library so it can hydrate the data
  const shapeDefinition = defineObjectShape(sqlAST)
  if (debug.enabled) {
    debug(emphasize('SHAPE_DEFINITION'), inspect(shapeDefinition))
  }
  return { sql, shapeDefinition }
}