Вставьте очень большой массив целых чисел в Oracle DB с помощью OCI, длинных инструкций SQL

#sql #c #oracle #oracle-call-interface

#sql #c #Oracle #oracle-call-interface

Вопрос:

Я беру на себя управление приложением OCI, в котором мне нужно вставить последовательность из 1414 целых чисел в таблицу. Предыдущий автор этого приложения в основном динамически создавал инструкции SQL insert вида

INSERT INTO <TABLE_NAME> (<COLUMNS...>) VALUES (<VALUES...>) через последовательность strcpys, sprintfs, strcats и т.д. и хранится в статически выделенном буфере char insertStatement[BUFLEN] , где BUFLEN принимает любой необходимый размер.

Затем оператор подготавливается и выполняется с использованием

 OCIStmtPrepare(...);
OCIStmtExecute(...);
  

До сих пор это работало нормально; однако теперь
У меня есть таблица со столбцом типа SDATA_ARRAY , определенным как

 create or replace TYPE     SDATA_ARRAY 
AS VARRAY(1414) OF integer;
  

и нужно как-то вставить 1414 коротких целых чисел в эту таблицу через OCI. Попытка использовать стратегию построения литеральных инструкций SQL на месте здесь терпит неудачу, поскольку результирующая строка имеет длину около 6000 символов, и попытка выполнить ее приводит к ошибке

Error msg: ORA-01704: string literal too long

Итак, мне явно нужно изменить стратегию здесь, и в любом случае использование strcats таким образом на самом деле является инъекцией vuln. Итак, решение, насколько я могу судить, заключается в использовании привязки. Я просматривал документацию Oracle по OCI наhttp://docs.oracle.com/cd/E11882_01/appdev.112/e10646/oci05bnd.htm#LNOCI050 и вроде понять, как привязываться к примитивным типам данных. Однако я не могу понять, как использовать функцию, подобную OCIBindByName() определяемому пользователем VARRAY типу. Я хочу попробовать что-то вроде следующего:

 unsigned short sdata[1414];
...
strcpy(insertStatement, "INSERT INTO TABLE(SDATA) VALUES (SDATA_ARRAY(:sdata));");
OCIStmtPrepare(stmthp, errhp, (text *) insertStatement, (ub4) strlen(insertStatement), 
               (ub4) OCI_NTV_SYNTAX, (ub4) OCI_DEFAULT);
// OCIBindByName(stmthp, amp;bindp, errhp, (text *) ":sdata", (sb4) strlen(":sdata"), 
//               sizeof(sdata), ???, ...);
OCIStmtExecute(svchp, stmthp, errhp, (ub4) 1, (ub4) 0, (OCISnapshot *) NULL, 
               (OCISnapshot *) NULL, OCI_DEFAULT);
  

очевидно, что необходимо проверять ошибки для каждого вызова.

  • Что я использую для ub2 dty параметра в OCIBindByName(), используемого для указания типа привязываемой переменной? (в отличие от SQLT_STR, SQLT_INT и т.д.) Нужно ли мне выполнять дополнительные вызовы OCI?

  • Есть ли лучший способ сделать этот оператор insert (используя строго OCI), который не приведет к получению ошибки длины? Возможно, постепенно?

  • Есть ли лучший способ сохранить эти 1414 целых чисел, чем тип VARRAY? (На самом деле не в моей власти изменить, кроме общения с администратором базы данных, однако …)

  • Наконец, позволит ли мне использование вызова привязки преодолеть ошибку длины?

У меня нет опыта работы с SQL, Oracle или OCI до этого проекта, единственная причина, по которой он был мне предоставлен, заключается в том, что мне здесь удобнее всего с C. Таким образом, любые советы / критические замечания / альтернативные идеи, относящиеся к тому, что я пытаюсь сделать, очень ценятся!

РЕДАКТИРОВАТЬ: эта страницаhttp://docs.oracle.com/cd/B14117_01/appdev.101/b10779/oci03typ.htm похоже, подразумевается, что константой типа для VARRAY является SQLT_NTY для «именованных типов данных», помещая его в ту же категорию, что и объекты и вложенные таблицы. Однако SQLT_NTY, похоже, сопоставляется со структурами в коде C, тогда как мои данные хранятся в массиве shorts. Я бы подумал, что это означает, что я должен обернуть массив в структуру, чтобы заставить это работать, возможно, используя переводчик типов объектов Oracle. Однако я все еще не уверен, как будет выглядеть оператор bind и нужно ли мне добавлять дополнительные шаги, такие как OCIBindObject().

Ответ №1:

Итак, чтобы работать с пользовательскими типами данных, такими как этот, OCI должен быть инициализирован в режиме объекта: OCIInitialize(OCI_OBJECT, ...)

Привязка массива к инструкции требует немного дополнительной работы. Решение, которое я в конечном итоге собрал по кусочкам (путем большого поиска и возврата), имеет общую форму:

 OCIArray *array = NULL;
OCIType *tdo = NULL;
OCIBind *bndp = NULL;

char *typeOwner = "OWNER"; // Whichever user owns the type definition
char *typeName = "SDATA_ARRAY";

OCITypeByName(envhp, errhp, svchp, (text *) typeOwner, strlen(typeOwner),
              (text *) typeName, strlen(typeName), NULL, 0, 
              OCI_DURATION_SESSION, OCI_TYPEGET_HEADER, amp;tdo);

OCIObjectNew(envhp, errhp, svchp, OCI_TYPECODE_VARRAY, tdo, NULL, 
             OCI_DURATION_SESSION, TRUE, amp;array);

for ( int i = 0; i < 1414; i   ) {
    OCINumber num_val;
    OCINumberFromInt(errhp, sdata[i], sizeof(unsigned short), 
                     OCI_NUMBER_UNSIGNED, amp;num_val);
    OCICollAppend(envhp, errhp, amp;num_val, NULL, array);
}
OCIStmtPrepare(stmthp, errhp, statement, strlen(statement),
               OCI_NTV_SYNTAX, OCI_DEFAULT);
OCIBindByName(stmthp, amp;bnpd, errhp, ":sdata", strlen(":sdata"), 
              NULL, 0, SQLT_NTY, NULL, 0, 0, 0, 0, OCI_DEFAULT);

OCIBindObject(bndp, errhp, tdo, amp;array, NULL, NULL, NULL);

OCIStmtExecute(svchp, stmthp, errhp, 1, 0, NULL, NULL, OCI_COMMIT_ON_SUCCESS);

OCIObjectFree(envhp, errhp, array, OCI_OBJECTFREE_FORCE);
  

(Я не включил проверку / обработку ошибок для краткости, но она есть.)

Это решение сначала извлекает пользовательский тип данных ( OCITypeByName(...) ) и создает его экземпляр. Далее, Oracle предоставляет набор функций для работы с общими коллекциями (такими как переменные или вложенные таблицы), поэтому я использовал один ( OCICollAppend(...) ) для итеративной загрузки моих данных в тип данных Oracle для представления переменных, OCIArray .

Затем я готовлю инструкцию как обычно и вызываю OCIBindByName(...) с SQLT_NTY в качестве кода типа. Насколько я понял, это код, используемый для любого пользовательского типа данных. За этим должен последовать вызов OCIBindObject(...) , который выполняет некоторую дополнительную работу, необходимую для пользовательских объектов.

Используя эти шаги, я смог успешно вставить данные. Вероятно, это не оптимальное решение, но я решил опубликовать то, что узнал, на случай, если у кого-то возникнет такая же проблема (поскольку документация OCI представляет собой гигантский запутанный беспорядок), или на случай, если кто-нибудь может предложить улучшения.