Создание настраиваемого драйвера ODBC
В моей текущей работе мы стремимся реализовать собственный драйвер odbc, чтобы позволить многим различным приложениям подключаться к нашему собственному приложению в качестве источника данных. Сейчас мы пытаемся взвесить варианты разработки нашего собственного драйвера для спецификации реализации, которая является массивной, или с использованием SDK, который позволяет программистам "заполнять" детали, специфичные для данных, и допускать более высокие уровни абстракции.
Кто-нибудь еще реализовал пользовательский драйвер odbc? В какие подводные камни вы столкнулись? Какие преимущества вы видели от этого? Сколько человеко-часов вы бы приблизили? Вы использовали SDK, и если да, то какие преимущества/недостатки вы видели из этого подхода?
Любые комментарии и ответы будут очень признательны. Спасибо!
EDIT: Мы пытаемся поддерживать переносимость с помощью нашего кода, который написан на C.
Ответы
Ответ 1
У меня нет, но я однажды взял интервью у компании, которая сделала именно это. Они сделали
продукт 4GL/СУБД под названием AMPS такой же архитектуры, как MUMPS - иерархическая база данных со встроенным 4GL (целый жанр таких систем вышел в 1970-х годах). У них была довольно значительная базовая база кода и клиенты, желающие подключиться к ней с помощью MS Access.
Ведущий разработчик, который взял у меня интервью, рассказал об этом несколько военных историй. По-видимому, это очень болезненно, и его нельзя воспринимать легкомысленно. Однако им действительно удалось воплотить его в жизнь.
Одной из альтернатив для этого было бы предоставление продукта данных/продукта BI (по линии SAP BW), который представляет ваши данные приложения во внешней базе данных и массирует его в более дружественный формат, такой как схема звезды или снежинки.
Это будет связано с отсутствием поддержки в режиме реального времени доступа, но может быть значительно проще реализовать (и, что более важно, поддерживать), чем драйвер ODBC. Если ваши требования к доступу в режиме реального времени достаточно предсказуемы и ограничены, вы могли бы предоставить API веб-службы для поддержки этих.
Ответ 2
Другой вариант: вместо создания ODBC-драйвера реализуйте обратный конец, в котором говорится о проводном протоколе, который использует другая база данных (например, Postgresql или MySQL).
Затем ваши пользователи могут загрузить и использовать, например, драйвер ODBC Postgresql.
Точно, какая база данных, которую вы выбрали для эмуляции, вероятно, должна зависеть от того, насколько хорошо документирован формат протокола.
Оба Postgres и MySQL имеют достойную документацию для своих клиент-серверных протоколов.
Ниже приведен пример простого примера Python 2.7 сервера, который понимает части проводного протокола Postgresql. Пример script создает сервер, который слушает порт 9876. Я могу использовать команду psql -h localhost -p 9876
для подключения к серверу. Любой выполненный запрос возвращает результирующий набор со столбцами abc и def и двумя строками, все значения NULL.
Чтение документов Postgresql и использование чего-то типа wireshark для проверки реального трафика протокола сделало бы довольно простым реализовать совместимый с Postgresql обратный конец.
import SocketServer
import struct
def char_to_hex(char):
retval = hex(ord(char))
if len(retval) == 4:
return retval[-2:]
else:
assert len(retval) == 3
return "0" + retval[-1]
def str_to_hex(inputstr):
return " ".join(char_to_hex(char) for char in inputstr)
class Handler(SocketServer.BaseRequestHandler):
def handle(self):
print "handle()"
self.read_SSLRequest()
self.send_to_socket("N")
self.read_StartupMessage()
self.send_AuthenticationClearText()
self.read_PasswordMessage()
self.send_AuthenticationOK()
self.send_ReadyForQuery()
self.read_Query()
self.send_queryresult()
def send_queryresult(self):
fieldnames = ['abc', 'def']
HEADERFORMAT = "!cih"
fields = ''.join(self.fieldname_msg(name) for name in fieldnames)
rdheader = struct.pack(HEADERFORMAT, 'T', struct.calcsize(HEADERFORMAT) - 1 + len(fields), len(fieldnames))
self.send_to_socket(rdheader + fields)
rows = [[1, 2], [3, 4]]
DRHEADER = "!cih"
for row in rows:
dr_data = struct.pack("!ii", -1, -1)
dr_header = struct.pack(DRHEADER, 'D', struct.calcsize(DRHEADER) - 1 + len(dr_data), 2)
self.send_to_socket(dr_header + dr_data)
self.send_CommandComplete()
self.send_ReadyForQuery()
def send_CommandComplete(self):
HFMT = "!ci"
msg = "SELECT 2\x00"
self.send_to_socket(struct.pack(HFMT, "C", struct.calcsize(HFMT) - 1 + len(msg)) + msg)
def fieldname_msg(self, name):
tableid = 0
columnid = 0
datatypeid = 23
datatypesize = 4
typemodifier = -1
format_code = 0 # 0=text 1=binary
return name + "\x00" + struct.pack("!ihihih", tableid, columnid, datatypeid, datatypesize, typemodifier, format_code)
def read_socket(self):
print "Trying recv..."
data = self.request.recv(1024)
print "Received {} bytes: {}".format(len(data), repr(data))
print "Hex: {}".format(str_to_hex(data))
return data
def send_to_socket(self, data):
print "Sending {} bytes: {}".format(len(data), repr(data))
print "Hex: {}".format(str_to_hex(data))
return self.request.sendall(data)
def read_Query(self):
data = self.read_socket()
msgident, msglen = struct.unpack("!ci", data[0:5])
assert msgident == "Q"
print data[5:]
def send_ReadyForQuery(self):
self.send_to_socket(struct.pack("!cic", 'Z', 5, 'I'))
def read_PasswordMessage(self):
data = self.read_socket()
b, msglen = struct.unpack("!ci", data[0:5])
assert b == "p"
print "Password: {}".format(data[5:])
def read_SSLRequest(self):
data = self.read_socket()
msglen, sslcode = struct.unpack("!ii", data)
assert msglen == 8
assert sslcode == 80877103
def read_StartupMessage(self):
data = self.read_socket()
msglen, protoversion = struct.unpack("!ii", data[0:8])
print "msglen: {}, protoversion: {}".format(msglen, protoversion)
assert msglen == len(data)
parameters_string = data[8:]
print parameters_string.split('\x00')
def send_AuthenticationOK(self):
self.send_to_socket(struct.pack("!cii", 'R', 8, 0))
def send_AuthenticationClearText(self):
self.send_to_socket(struct.pack("!cii", 'R', 8, 3))
if __name__ == "__main__":
server = SocketServer.TCPServer(("localhost", 9876), Handler)
try:
server.serve_forever()
except:
server.shutdown()
Пример командной строки psql session:
[~]
$ psql -h localhost -p 9876
Password:
psql (9.1.6, server 0.0.0)
WARNING: psql version 9.1, server version 0.0.
Some psql features might not work.
Type "help" for help.
codeape=> Select;
abc | def
-----+-----
|
|
(2 rows)
codeape=>
Драйвер ODBC, который говорит протокол Postgresql, должен работать (но я еще не пробовал).
Ответ 3
Драйверы ODBC очень сложны - решение написать одно не следует воспринимать легкомысленно. Обзор существующих драйверов с открытым исходным кодом является хорошим подходом к примерам, но большинство из них имеют недостатки, которые вы не хотите эмулировать:). API одинаковы независимо от платформы ОС.
FreeTDS для MSSQL/Sybase имеет одну из лучших версий ODBC-драйверов с открытым исходным кодом, которые я видел.
Если вы контролируете приложение, вы можете избавиться от реализации того, что может быть очень маленьким подмножеством спецификации за разумное время. Для использования в среде общего назначения может потребоваться немало усилий, чтобы получить право. Сверху моей головы в дополнение к простому выполнению десятков вызовов оболочки вам также придется реализовать:
- Функции доступа к метаданным
- Анализ синтаксиса синтаксиса специальных запросов ODBC
- Сопоставления сообщений SQLSTATE
- Маршрутизация Multibyte/Character set
- Поддержка версии ODBC версии 2,3 - сообщения об ошибках/сопоставления функций
- курсоры
- Пользовательский интерфейс конфигурации DM для управления источником данных
Ответ 4
Я не реализовал драйвер ODBC, но просто хотел предложить предложение о том, что вы можете начать с реализации с открытым исходным кодом и добавить свои собственные настройки. Это может заставить вас начать намного быстрее.
Есть как минимум два варианта:
-
unixODBC лицензируется в соответствии с LGPL, а это означает, что если вы измените код, вы должны внести свои изменения в open-source.
-
iODBC лицензируется под LGPL или New BSD по вашему выбору. Новый BSD позволяет вносить изменения без внесения изменений в open-source.
Однако неясно, работают ли эти пакеты в Windows, а не работает в UNIX/Linux с клиентским API, совместимым со стандартным ODBC. Вы не указываете, какую платформу вы используете, поэтому я не знаю, относится ли это к вам.
Ответ 5
Дополнительный ввод на этом: Обратите внимание, что это не так сложно, как использование SDK. Как предложил Кайл, создание пользовательского драйвера odbc уже не так сложно. С таким решением, как OpenAccess, 99% кода уже предоставлено, и вы реализуете только 12 функций. Вы можете выбрать любой из следующих языков для написания кода: C/С++, Java,.NET, С#, ABL или 4GL. Для получения дополнительной информации о начале работы читайте этот блог: https://www.progress.com/blogs/quick-guide-build-custom-odbc-driver-in-java-or-c
Ответ 6
Это сообщение немного устарело, но стоит упомянуть, что если вам нужен драйвер ODBC, вы можете использовать SDK, например: http://www.simba.com/drivers/simba-engine-sdk/ Он заботится о большинстве точек, поднятых в других ответах, и дает вам упрощенный интерфейс для реализации.
Я работаю для Simba, поэтому я немного предвзятый, но с помощью SDK довольно легко создать драйвер ODBC для всего, что вы пытаетесь сделать. Вы можете получить что-то в течение 5 дней, если вы немного владеете кодированием.
Один из других сообщений рекомендует использовать unixODBC или iODBC в качестве отправной точки, однако это не сработает. Важно осознать различие между диспетчером драйверов (unixODBC, iODBC и т.д.) И драйвером. Диспетчер драйверов выступает в роли посредника между приложением и драйвером, устраняя необходимость прямого привязки к драйверу.
Вы можете начать с драйверов Postgres или MySQL в качестве отправной точки и развернуть их для использования своей собственной базы данных, однако это вряд ли будет тривиальной задачей. Создание драйвера с нуля еще сложнее и, вероятно, будет иметь текущие (и более высокие, чем ожидалось) затраты на обслуживание. До тех пор, пока вы осознаете стоимость этого подхода, он также может быть жизнеспособным.