private CompiledStatement sqlCompileStatement(String sql, int type) throws HsqlException {

    Tokenizer tokenizer;
    String token;
    Parser parser;
    int cmd;
    CompiledStatement cs;
    boolean isCmdOk;

    tokenizer = new Tokenizer(sql);
    parser = new Parser(dDatabase, tokenizer, this);
    token = tokenizer.getString();
    cmd = Token.get(token);
    isCmdOk = true;

    switch (cmd) {
      case Token.SELECT:
        {
          cs = parser.compileSelectStatement(null);

          break;
        }
      case Token.INSERT:
        {
          cs = parser.compileInsertStatement(null);

          break;
        }
      case Token.UPDATE:
        {
          cs = parser.compileUpdateStatement(null);

          break;
        }
      case Token.DELETE:
        {
          cs = parser.compileDeleteStatement(null);

          break;
        }
      case Token.CALL:
        {
          if (type != CompiledStatement.CALL) {
            throw Trace.error(Trace.ASSERT_FAILED, "not a CALL statement");
          }

          cs = parser.compileCallStatement(null);

          break;
        }
      default:
        {
          isCmdOk = false;
          cs = null;

          break;
        }
    }

    // In addition to requiring that the compilation was successful,
    // we also require that the submitted sql represents a _single_
    // valid DML statement.
    if (!isCmdOk) {
      throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
    }

    // fredt - now accepts semicolon and whitespace at the end of statement
    // fredt - investigate if it should or not
    while (tokenizer.getPosition() < tokenizer.getLength()) {
      token = tokenizer.getString();

      Trace.check(
          token.length() == 0 || token.equals(Token.T_SEMICOLON), Trace.UNEXPECTED_TOKEN, token);
    }

    // - need to be able to key cs against its sql in statement pool
    // - also need to be able to revalidate its sql occasionally
    cs.sql = sql;

    return cs;
  }