static void cg_declare_cursor()

in sources/cg_c.c [5300:5430]


static void cg_declare_cursor(ast_node *ast) {
  Contract(is_ast_declare_cursor(ast));
  EXTRACT_ANY_NOTNULL(name_ast, ast->left);
  EXTRACT_STRING(cursor_name, name_ast);

  bool_t out_union_proc = false;
  bool_t is_boxed = !!(name_ast->sem->sem_type & SEM_TYPE_BOXED);
  bool_t is_unboxing = is_ast_str(ast->right);

  if (is_ast_call_stmt(ast->right)) {
    out_union_proc = has_out_union_stmt_result(ast);
  }

  // only one of these (is boxed makes no sense with out union)
  Invariant(!out_union_proc || !is_boxed);

  // can't be both of these either
  Invariant(!out_union_proc || !is_unboxing);

  // unboxing implies is_boxed   a->b <==> (!a | b)
  Invariant(!is_unboxing || is_boxed);

  if (out_union_proc) {
    EXTRACT_STRING(name, ast->right->left);

    CG_CHARBUF_OPEN_SYM(result_ref, name, "_result_set_ref");

    bprintf(cg_declarations_output, "%s %s_result_set_ = NULL;\n", result_ref.ptr, cursor_name);
    bprintf(cg_declarations_output, "%s %s_row_num_ = 0;\n", rt->cql_int32, cursor_name);
    bprintf(cg_declarations_output, "%s %s_row_count_ = 0;\n", rt->cql_int32, cursor_name);
    bprintf(cg_cleanup_output, "  cql_object_release(%s_result_set_);\n", cursor_name);

    if (cg_in_loop) {
      // tricky case, the call might iterate so we have to clean up the cursor before we do the call
      bprintf(cg_main_output, "cql_object_release(%s_result_set_);\n", cursor_name);
    }

    CHARBUF_CLOSE(result_ref);
  }
  else {
    bprintf(cg_declarations_output, "sqlite3_stmt *%s_stmt = NULL;\n", cursor_name);

    if (!is_boxed) {
      // easy case, no boxing, just finalize on exit.
      bprintf(cg_cleanup_output, "  cql_finalize_stmt(&%s_stmt);\n", cursor_name);

      if (cg_in_loop) {
        // tricky case, the call might iterate so we have to clean up the cursor before we do the call
        bprintf(cg_main_output, "cql_finalize_stmt(&%s_stmt);\n", cursor_name);
      }
    }
  }

  if (is_select_stmt(ast->right)) {
    // DECLARE [name] CURSOR FOR [select_stmt]
    // or
    // DECLARE [name] CURSOR FOR [explain_stmt]
    EXTRACT_ANY_NOTNULL(select_stmt, ast->right);

    if (is_boxed) {
      // The next prepare will finalize the statement, we don't want to do that
      // if the cursor is being handled by boxes. The box downcount will take care of it
      bprintf(cg_main_output, "%s_stmt = NULL;\n", cursor_name);
    }
    cg_bound_sql_statement(cursor_name, select_stmt, CG_PREPARE|CG_MINIFY_ALIASES);
  }
  else if (is_unboxing) {
    // DECLARE [name] CURSOR FOR [named_box_object]

    CHARBUF_OPEN(box_name);
    bprintf(cg_main_output, "%s_stmt = cql_unbox_stmt(%s);\n", cursor_name, ast->right->sem->name);
    bprintf(&box_name, "%s_object_", cursor_name);
    cg_copy(cg_main_output, box_name.ptr, SEM_TYPE_OBJECT, ast->right->sem->name);
    CHARBUF_CLOSE(box_name);
  }
  else {
    // DECLARE [name] CURSOR FOR [call_stmt]]
    if (is_boxed) {
      // The next prepare will finalize the statement, we don't want to do that
      // if the cursor is being handled by boxes. The box downcount will take care of it
      bprintf(cg_main_output, "%s_stmt = NULL;\n", cursor_name);
    }

    EXTRACT_NOTNULL(call_stmt, ast->right);
    cg_call_stmt_with_cursor(call_stmt, cursor_name);
  }

  if (is_boxed) {
    // An object will control the lifetime of the cursor.  If the cursor is boxed
    // this is the object reference that will be used.  This way the exit path is
    // uniform regardless of whether or not the object was in fact boxed in the
    // control flow.  This is saying that it might be boxed later so we use this
    // general mechanism for lifetime. The cg_var_decl helper handles cleanup too.

    CHARBUF_OPEN(box_name);
    bprintf(&box_name, "%s_object_", cursor_name);

    cg_var_decl(cg_declarations_output, SEM_TYPE_OBJECT, box_name.ptr, CG_VAR_DECL_LOCAL);

    // the unbox case gets the object from the unbox operation above, so skip if unboxing

    if (!is_unboxing) {
      // Note we have to clear the stashed box object and then accept the new box without
      // increasing the retain count on the new box because it starts with a +1 as usual.
      // This is a job for cg_copy_for_create!

      CHARBUF_OPEN(box_value);
      bprintf(&box_value, "cql_box_stmt(%s_stmt)", cursor_name);
      cg_copy_for_create(cg_main_output, box_name.ptr, SEM_TYPE_OBJECT, box_value.ptr);
      CHARBUF_CLOSE(box_value);
    }

    CHARBUF_CLOSE(box_name);
  }

  if (name_ast->sem->sem_type & SEM_TYPE_HAS_SHAPE_STORAGE) {
    cg_declare_auto_cursor(cursor_name, name_ast->sem);
  }
  else {
    // if it's a global cursor (`!in_proc`) we have to assume we will need the
    // variable eventually so we emit it now; if it's a local then we only emit
    // it if we know it'll be used later on (as indicated by `SEM_TYPE_FETCH_INTO`)
    if (!in_proc || name_ast->sem->sem_type & SEM_TYPE_FETCH_INTO) {
      // make the cursor_has_row hidden variable
      CHARBUF_OPEN(temp);
      bprintf(&temp, "_%s_has_row_", cursor_name);
      cg_var_decl(cg_declarations_output, SEM_TYPE_BOOL | SEM_TYPE_NOTNULL, temp.ptr, CG_VAR_DECL_LOCAL);
      CHARBUF_CLOSE(temp);
    }
  }
}