Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Sybase ASA, ASE, IQ [игнор отключен] [закрыт для гостей] / ASA: получить список подключенных юзеров / 11 сообщений из 11, страница 1 из 1
11.01.2006, 13:51
    #33476455
Vladimir Kozlov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Возникла необходимость в хранимой процедуре получить список подключенных пользователей (в виде рекордсета, чтоб потом по нему курсором пробежаться). Как?

Или, альтернативный вариант - при подключении пользователя присвоить его коннекту какой-то GUID и в дальнейшем иметь возможность из других коннектов проверять - жив пользователь с этим гуидом или уже отвалился.

Суть того что мне в конечном итоге нужно: при открытии пользователем документа в базе нужно в какую-то таблицу положить идентификатор пользователя и идентификатор документа, при закрытии - запись стереть. Другой пользователь, если в этой таблице есть идентификатор документа и создалась она не в его коннекте - получает отлуп или выполняется какое-то действие. При попытке открыть документ, если в этой таблице присутствует запись а пользователь зафиксированный в этой записи отвалился - запись грохнуть и документ разблокировать. То есть поведение примерно аналогичное как при параллельном открытии документов в хранилище MS Exchange :)
Стандартный механизм блокировок не катит - и структура документа сложная, и трехслойка планируется.

Надеюсь понятно написал :)
...
Рейтинг: 0 / 0
11.01.2006, 15:33
    #33476808
Рыжий Кот
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
список вроде так можно получить
Код: plaintext
sa_conn_properties_by_conn('Number')
...
Рейтинг: 0 / 0
11.01.2006, 16:36
    #33477023
ASCRUS
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Список пользователей не нужен, все в ASA делается гораздо проще.

1. Делаем таблицу хранения логических блокировок:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
CREATE TABLE Doc_Lock (
  Table_Name char( 128 ) NOT NULL,
  Doc_id char( 128 ) NOT NULL,
  User_Name char( 128 ) NOT NULL,
  PRIMARY KEY (Table_Name, Doc_id)
);
CREATE INDEX User_Name ON Doc_Lock (User_Name);

2. Делаем процедуру добавления/снятия блокировки:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
CREATE PROCEDURE sp_Doc_Lock (
  IN @Table_Name char( 128 ),
  IN @Doc_id char( 128 ),
  IN @Lock_Type char( 1 ),
  IN @User_Name char( 128 ) DEFAULT NULL
)
BEGIN
  DECLARE @Cur_User_Name char( 128 );

  IF IsNull(@Lock_Type, '') NOT IN ('+', '-')
  THEN
    RAISERROR  20000  'Указан неизвестный режим блокировки "' || @Lock_Type || '"';
    RETURN;
  END IF;

  SET @User_Name = IsNull(@User_Name, CURRENT USER);

  IF @Lock_Type = '+'
  THEN
    SELECT User_Name
    INTO @Cur_User_Name
    FROM Doc_Lock
    WHERE Table_Name = @Table_Name AND Doc_id = @Doc_id;

    IF @Cur_User_Name IS NOT NULL AND @Cur_User_Name <> @User_Name
    THEN
      RAISERROR  20000  'Документ уже заблокирован пользователем "' || @Cur_User_Name || '"';
      RETURN;
    END IF;

    IF @Cur_User_Name IS NULL
    THEN
      INSERT INTO Doc_Lock (Table_Name, Doc_id, User_Name)
      VALUES (@Table_Name, @Doc_id, @User_Name);
    END IF;

    COMMIT;
  ELSE
    DELETE FROM Doc_Lock
    WHERE Table_Name = @Table_Name AND Doc_id = @Doc_id AND User_Name = @User_Name;

    IF @@ROWCOUNT =  0 
    THEN
      ROLLBACK;
      RAISERROR  20000  'Блокировка документа отсутствует';
      RETURN;
    END IF;

    COMMIT;
  END IF;
END;

3. Вызываем блокировку и разблокировку документов где нужно, указывая имя таблицы и код записи:
Код: plaintext
1.
2.
3.
4.
...
CALL sp_Doc_Lock (@Table_Name = 'Table1', @Doc_id =  1 , @Lock_Type = '+');
...
CALL sp_Doc_Lock (@Table_Name = 'Table1', @Doc_id =  1 , @Lock_Type = '-');
...

4. Создаем событие на отключение пользователя, чтобы потереть его блокировки, которые он не успел снять (в случае экстренного отключения сессии):
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
CREATE EVENT "Doc_Lock_Proccess_Disconnect" TYPE "Disconnect"
HANDLER
BEGIN
  DECLARE @User_Name char( 128 );
  SET @User_Name = EVENT_PARAMETER('User');

  DELETE FROM Doc_Lock
  WHERE User_Name = @User_Name;

  COMMIT;
END;

Вуаля, система логических блокировок готова, другие РСУБД могут кусать локти
...
Рейтинг: 0 / 0
11.01.2006, 17:02
    #33477125
Vladimir Kozlov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
ASCRUS спасибо огромное!

А можно предусмотреть ситуацию, когда два юзера зашли под одним логином? то есть существует ли какой-то глобальный идентификатор коннекта в базе, который можно поюзать внутри процедуры?
...
Рейтинг: 0 / 0
11.01.2006, 17:31
    #33477241
Рыжий Кот
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Наверное нужно использовать EVENT_PARAMETER ('')
...
Рейтинг: 0 / 0
11.01.2006, 17:32
    #33477244
Рыжий Кот
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
сори, выпало :(
EVENT_PARAMETER('ConnectionID')
...
Рейтинг: 0 / 0
11.01.2006, 17:37
    #33477262
ASCRUS
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Рыжий Котсори, выпало :(
EVENT_PARAMETER('ConnectionID')
Угу, в таблице сделать тип еще Connect_id unsigned int, в процедуре вместо User_Name и CURRENT USER писать Connect_id и CONNECTION_PROPERTY('Number'), в событии соотвествующе Connect_id и EVENT_PARAMETER('ConnectionID').
...
Рейтинг: 0 / 0
11.01.2006, 18:32
    #33477403
ASCRUS
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Полный, по идее работоспособный скрипт для FAQ:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
CREATE TABLE "DBA"."Doc_Lock" (
  "Table_Name" char( 128 ) NOT NULL,
  "Doc_id" char( 128 ) NOT NULL,
  "User_Name" char( 128 ) NOT NULL DEFAULT current user,
  "Connect_id" unsigned int NOT NULL,
  "Lock_Date" timestamp NOT NULL DEFAULT current timestamp,
  PRIMARY KEY ( "Table_Name", "Doc_id" )
);
COMMENT ON TABLE "DBA"."Doc_Lock" IS 'Логические блокировки';
COMMENT ON COLUMN "DBA"."Doc_Lock"."Table_Name" IS 'Имя таблицы';
COMMENT ON COLUMN "DBA"."Doc_Lock"."Doc_id" IS 'Код документа';
COMMENT ON COLUMN "DBA"."Doc_Lock"."User_Name" IS 'Логин пользователя';
COMMENT ON COLUMN "DBA"."Doc_Lock"."Connect_id" IS 'Код сессии';
COMMENT ON COLUMN "DBA"."Doc_Lock"."Lock_Date" IS 'Время начала блокировки';

CREATE PROCEDURE "DBA"."sp_Doc_Lock" (
  IN @Table_Name char( 128 ),
  IN @Doc_id char( 128 ),
  IN @Lock_Type char( 1 ),
  IN @AutoCommit bit DEFAULT  0 ,
  IN @RaisError bit DEFAULT  1 
)
BEGIN
  DECLARE @Connect_id unsigned int;
  DECLARE @Cur_Connect_id unsigned int;
  DECLARE @Cur_User_Name char( 128 );
  DECLARE @Lock_Date timestamp;

  IF IsNull(@Lock_Type, '') NOT IN ('+', '-')
  THEN
    IF @RaisError =  1 
    THEN
      RAISERROR  20000  'Указан неизвестный режим блокировки "' || @Lock_Type || '"';
    END IF;
    RETURN - 1 ;
  END IF;

  SET @Connect_id = CONNECTION_PROPERTY('Number');

  IF @Lock_Type = '+'
  THEN
    SELECT Connect_id, User_Name, Lock_Date
    INTO @Cur_Connect_id, @Cur_User_Name, @Lock_Date
    FROM DBA.Doc_Lock
    WHERE Table_Name = @Table_Name AND Doc_id = @Doc_id;

    IF @Cur_Connect_id IS NOT NULL AND @Cur_Connect_id <> @Connect_id
    THEN
      IF @AutoCommit =  1 
      THEN
        ROLLBACK;
      END IF;
      IF @RaisError =  1 
      THEN
        RAISERROR  20000  'Документ уже заблокирован пользователем "' || @Cur_User_Name || '" (' ||
                        @Cur_Connect_id || ') с ' || DateFormat(@Lock_Date, 'dd.mm.yyyy hh:mm');
      END IF;
      RETURN - 1 ;
    END IF;

    IF @Cur_Connect_id IS NULL
    THEN
      INSERT INTO DBA.Doc_Lock (Table_Name, Doc_id, Connect_id)
      VALUES (@Table_Name, @Doc_id, @Connect_id);
    END IF;

    IF @AutoCommit =  1 
    THEN
      COMMIT;
    END IF;
  ELSE
    DELETE FROM DBA.Doc_Lock
    WHERE Table_Name = @Table_Name AND Doc_id = @Doc_id AND Connect_id = @Connect_id;

    IF @@ROWCOUNT =  0 
    THEN
      IF @AutoCommit =  1 
      THEN
        ROLLBACK;
      END IF;

      IF @RaisError =  1 
      THEN
        RAISERROR  20000  'Блокировка документа отсутствует';
      END IF;
      RETURN - 1 ;
    END IF;

    IF @AutoCommit =  1 
    THEN
      COMMIT;
    END IF;
  END IF;
END;
COMMENT ON PROCEDURE "DBA"."sp_Doc_Lock" IS 'Заблокировать или разблокировать документ';

CREATE EVENT "Doc_Lock_DB_Startup" TYPE "DatabaseStart"
HANDLER
BEGIN 
  TRUNCATE TABLE DBA.Doc_Lock;
  COMMIT;
END;
COMMENT ON EVENT "Doc_Lock_DB_Startup" IS 'Очистить блокировки при запуске БД';

CREATE EVENT "Doc_Lock_Proccess_Disconnect" TYPE "Disconnect"
HANDLER
BEGIN 
  DECLARE @Connect_id unsigned int;
  SET @Connect_id = EVENT_PARAMETER('ConnectionID');

  DELETE FROM DBA.Doc_Lock
  WHERE Connect_id = @Connect_id;

  COMMIT;
END;
COMMENT ON EVENT "Doc_Lock_Proccess_Disconnect" IS 'Удаление логических блокировок по отключаемому пользователю';
...
Рейтинг: 0 / 0
11.01.2006, 18:43
    #33477422
Vladimir Kozlov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Огромное спасибо за помощь. Уже и сделал, и внедрил :)
...
Рейтинг: 0 / 0
11.01.2006, 19:05
    #33477466
ASCRUS
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Vladimir KozlovОгромное спасибо за помощь. Уже и сделал, и внедрил :)
Чем ASA и радует - если знаешь, то чуть кода, все работает и никакого геммороя, плясок с бубнами или прикручивания кода со стороны клиента. Кстати советую обратить внимание на один ньюанс - я не зря ввел в процедуру параметр AutoCommit, по идее лучше всего этот флаг не выставлять, т.е. чтобы само клиентское приложение явно завершало транзакцию. Это гарантирует, что если во время получения или сохранения данных произойдет ошибка, то не получится, что блокировка к примеру осталась висеть, а SELECT не отработал и клиент ничего не получил.

Логика на клиенте должна быть такая (есественно никаких AutoCommit на клиенте):
1. Вызываем процедуру для блокировки документа
2. Возвращаем данные на клиента
3. Если все успешно, то COMMIT, иначе ROLLBACK и выход
4. Редактирование документа
5. Сохранение документа
6. Если не успешно, то ROLLBACK и возврат на пункт 4
7. Вызываем процедуру для разблокировки документа
8. Если все успешно, то COMMIT, иначе ROLLBACK и возврат на пункт 4

Далее для пункта 7 стоит учесть один существенный момент - если на время редактирования документа сессия отвалится, то естественно блокировка с документа снимется и пункт 8 вызовет ошибку, что в принципе правильно, так как даже если сессия обратно и соединится на момент сохранения данных, то у нее уже будет другой идентификатор и нет гарантии, что за момент отвала сессии, другой пользователь уже не отредактировал или сейчас редактирует данный документ. Соотвествующе, по хорошему, как не будет обидно пользователю, разрешить ему уже сохранение документа в таком случае нельзя, он может только отменить изменения и заново открыть для изменения документ. Однако есть решение данной проблемы: для пункта 7 при вызове процедуры можно выставить флаг @RaisError в ноль и таким образом после пересоединения сессии, он все таки сможет удачно пересохранить документ, так как в процедуре не будет возбуждена ошибка. Здесь, чтобы исключить ситуацию неявной перезаписи измененного уже другим пользователем документа на время отвала сессии, необходимо в изменяемую таблицу самого документа добавить поле LastTime TIMESTAMP с DEFAULT TIMESTAMP и считывать его тоже на редактирование, а при сохранении документа проверять по этому полю:
Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
UPDATE Table
SET ...
WHERE ID = @ID AND LastTime = @LastTime;

IF @@ROWCOUNT =  0 
THEN
  RAISERROR  20000  'Извините, но документ уже был изменен другим пользователем';
END IF;
Таким образом даже после отвала сессии, пользователь сможет заново подцепишись к БД все таки сохранить проделанные изменения, при условии, что эту запись никто не изменил с того момента, как он открыл ее на редактирование. Все это конечно действительно только при условии, что клиентское приложение позволяет работать в отложенном режиме без подключения к БД и таким образом можно будет восстановить сессию, без переоткрытия наборов данных.
...
Рейтинг: 0 / 0
11.01.2006, 19:14
    #33477482
ASCRUS
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
ASA: получить список подключенных юзеров
Ну и уж как последний штрих - если вдруг не дай бог клиент получает долго или много данных, то, чтобы другие клиентские приложения при попытке открыть этот же документ не ждали, пока первый клиент все получит, в процедуре лучше в запрос проверки на блокировку документа поставить хинт грязного чтения (вот где оно и пригодилось):
Код: plaintext
1.
2.
3.
    SELECT Connect_id, User_Name, Lock_Date
    INTO @Cur_Connect_id, @Cur_User_Name, @Lock_Date
    FROM asc_sys.Doc_Lock WITH (READUNCOMMITTED)
    WHERE Table_Name = @Table_Name AND Doc_id = @Doc_id;
Таким образом, пока первый клиент на документ еще открывает данные, следующие уже сразу получат отлуп, если попытаются заблокировать этот же документ.
...
Рейтинг: 0 / 0
Форумы / Sybase ASA, ASE, IQ [игнор отключен] [закрыт для гостей] / ASA: получить список подключенных юзеров / 11 сообщений из 11, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]