#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 представляет собой гигантский запутанный беспорядок), или на случай, если кто-нибудь может предложить улучшения.