Дилемма

Возникла забавная ситуация, в которой неправильное решение более притягательно, чем правильное. Отчасти «потому что могу».

В OLE DB запрещен вызов метода IDBInitialize::Uninitialize, если у источника данных есть открытые дочерние объекты (сессии, команды, наборы рядов).

It is an error to call IDBInitialize::Uninitialize when there are open sessions, commands, or rowsets on the data source object; that is, the consumer must release all interface pointers on all sessions, commands, and rowsets on the data source object before calling IDBInitialize::Uninitialize.

Я бы тут вместо «open» написал бы «not released», но поскольку дальше русским по-белому написано «the consumer must release all interface pointers on all sessions, commands, and rowsets on the data source object», то вроде как всякая двусмысленность исчезает.

Есть еще аналогичный метод IDBDataSourceAdmin::DestroyDataSource.

Any open OLE DB objects on this data source, such as sessions, commands, rowsets, rows, and views, must be released before calling this method.

IBProvider v3 кладет на эти правила и разрешает эти операции при наличии у Data Source дочерних объектов. Но к этому я вернусь чуть попозже.

Если собираетесь читать дальше, то приготовьте пару стаканов…

Когда я впервые вникал в эти правила 19 лет назад, мне они показались реально глупыми. Все эти запреты (в программировании) по молодости кажутся тупорылыми. Сейчас отношение поменялось на строго противоположную точку зрения. Я с радостью зарежу любую спорную функциональность.

В обозначенных правилах для Uninitialize и DestroyDataSource есть определенный резон смысл.

Первое. Дочерние объекты используют настройки инициализации источника данных.

Например я указал в строке подключения «auto_commit=tue», сессия скопирует эту настройку к себе и будет использовать как значение по-умолчанию для своего свойства «Session AutoCommit».

После отключения от базы данных с неосвобожденной сессией мы приходим к выбору между следующими вариантами:

A. Нужно запретить повторную инициализацию источника данных, пока не будут освобождены указатели на дочерние объекты, созданные в рамках предыдущего подключения.

Б. Разрешаем повторное подключение и оставляем старые дочерние объекты с настройками от предыдущего подключения.

В. Разрешаем повторное подключение и обновляем настройки дочерних объектов от предыдущего подключения.

Лично мне больше нравится вариант А. IBP v3 работает по варианту Б. Вариант В выглядит мутным.

Напомню (себе в первую очередь) — сама спецификация OLEDB вообще запрещает вызов Uninitialize в этом случае.

Второе. Когда Data Source запрещает Uninitialize и DestroyDataSource при наличии дочерних объектов, эти дочерние объекты могут этим пользоваться в полный рост. Например для безблокировочного обращения к константным данным источника данных, возникающим после инициализации.

Это достаточно крутая возможность, упрощающая код.

Что у нас имеется на текущий момент в v3.

Сейчас IBProvider v3 разрешает переход в неинициализированное состояние при неосвобожденных дочерних объектах источника данных.

Более того, он поддерживает запрос «DROP DATABASE», который по факту позволяет дочернему объекту (команде) переводить источник данных в это самое неинициализированное состояние.

Вообще выглядит круто.

У «DROP DATABASE», правда есть небольшой конфликт со стандартным пулом подключений. Сам пул запрещает интерфейс IDBDataSourceAdmin, в котором определен метод DestroyDataSource. И еще он ожидает доступность свойства DBPROP_RESETDATASOURCE набора DBPROPSET_DATASOURCE, который (по стандарту) доступен только у инициализированного источника данных.

В тройке реализован хак, который позволяет работать со свойством DBPROPSET_DATASOURCE::DBPROP_RESETDATASOURCE неинициализированного источника данных. Есть еще связанное свойство DBPROPSET_DATASOURCEINFO::DBPROP_CONNECTIONSTATUS. Но его провайдер у неинициализированного источника данных читать не дает, что выглядит как недоработка хака.

То есть после того как мы начали нарушать правило для DestroyDataSource, нужно нарушать правила для наборов свойств источника данных. А еще нужно делать выбор между вариантами А/Б/В. Работа по варианту Б выглядит топорно, нужно делать по варианту А.

Все это ведет к усложнению кода…

Короче, к концу написания этой заметки, я больше склоняюсь к тому, что:

Думаю, что тот, кто сформулировал те два простых правила в начале этой заметки, тоже пришел к такому выводу.

Поэтому я решил поддержку «DROP DATABASE» зарезать. И соблюдать правила приличия Uninitialize и DestroyDataSource.

Не, я еще пока могу. Но уже не хочу.

Последствия.
1. Через ADODB без поддержки «DROP DATABASE» базу не удалишь. В компонентах ADOX такая возможность не предусмотрена (проклятье в адрес его создателей).

Поэтому надо будет написать собственную поддержку.

2. Сломалось удаление базы данных через мой ADO.NET провайдер. Он сразу после инициализации источника данных создает сессию, и эта сессия мешает работе DestroyDataSource.

Автора, конечно, хочется придушить, но тогда некому будет чинить этот код.

В целом, эти «внезапные» доработки выглядят не очень сложными. Поэтому Карфаген будет разрушен будем хаки удалять и играть по правилам.

Вот.

Leave a Comment