dbc - объектная работа с базами данных

Описание: Разработка и отладка приложений. Упор на 3D-графику.

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#1 dyvniy » Пт, 22 января 2016, 16:37:44

Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#2 dyvniy » Ср, 3 февраля 2016, 11:46:24

ORDER BY
http://odb-users.codesynthesis.narkive.com/gM0nyX ... y-clause-in-odb-query-language
Спойлер
Discussion:
ORDER BY clause in ODB query language
(too old to reply)
Markus Klemm 5 months ago
PermalinkRaw MessageReport Is there a way to get a query with an ORDER BY clause with the typed checked, static, non-native ODB query language?
The examples in the manual is using the database system-native query language e.g. a concatenated string. That also makes it hard to use the ORDER BY clause because after the odb macro for the column, I need to concatenate the DESC/ASC clause but can’t due type mismatch.

Regards/ Mit freundlichen Grüßen

Markus Klemm
Boris Kolpackov 5 months ago
PermalinkRaw MessageReport Hi Markus,
Post by Markus Klemm
Is there a way to get a query with an ORDER BY clause with the typed
checked, static, non-native ODB query language? The examples in the
manual is using the database system-native query language e.g. a
concatenated string.
Currently you have to spell "ORDER BY" as a string, though the column
can be specified as a C++ name:

db->query<person> ((query::first == "John") + "ORDER BY" + query::age);

We do plan to provide some syntactic sugar for this, though I am not
sure how much static type checking we will be able to do. For example,
should we assume if the C++ type provides operator<, then we can do
ORDER BY?
Post by Markus Klemm
That also makes it hard to use the ORDER BY clause because after
the odb macro for the column, I need to concatenate the DESC/ASC
clause but can’t due type mismatch.
This should work:

db->query<person> ((...) + "ORDER BY" + query::age + "ASC");

BTW, "the odb macro for the column" is not a macro, it is a proper
C++ variable name.

Boris
Markus Klemm 5 months ago
PermalinkRaw MessageReport I was close to start a new thread, but it basicly the same topic:
My point in the previous question was, that I can't solve this use case below, without choosing the column by a string, rather than by a odb::query_column,yet?!

I didn't minimized it completly because @Boris was wondering about the use cases. So obvouisly I'm not a fan of the one suggested future solution of using operator<, because ordering criteria changes/ depens on the column. This is the real usecase when using the framework Wt (e.g. part of the user supplied model for a table view).

Raw Sourcecode below, gist here: https://gist.github.com/Superlokkus/a994d42534beca9ed5c6
void sort(int column, Wt::SortOrder order = Wt::AscendingOrder){
odb::query<directory> first_part("ORDER BY"), middle_part,end_part;
//odb::query_column<directory> middle_type; and decltype(odb::query<directory>::added) middle_type; were my next guesses
switch (column){
default:
middle_part = odb::query<directory>::added;/*Error: no operator "=" matches these operands
operand types are : odb::query<directory, odb::mssql::query_base> = const odb::mssql::query_column<boost::posix_time::ptime, odb::mssql::id_datetime>*/
break;
}
switch (order){
case Wt::DescendingOrder:
end_part = odb::query<directory>("DESC");
break;
default:
end_part = odb::query<directory>("ASC");
break;
}
odb::query<directory> final_query(first_part + middle_part + end_part);
}

Best Regards

Markus Klemm
...
Boris Kolpackov 5 months ago
PermalinkRaw MessageReport Hi Markus,
Raw Sourcecode below [...]
Wouldn't something like this work:

using dir_query = odb::query<directory>;

dir_query q ("ORDER BY");

switch (column)
{
case added:
q += dir_query::added;
...
}

switch (order)
{
case desc:
q += "DESC";
...
}

db.query (q);

Boris
Markus Klemm 5 months ago
PermalinkRaw MessageReport Hi Boris,

I didn't use the += operator, because it causes compiler errors for at least the wstring members. Surprisingly the bit type works.
I updated the according gist ( https://gist.github.com/Superlokkus/a994d42534beca9ed5c6 ), added the erros to the relevant lines and also added the object definition.

But as always, already a big thank you, for your efforts and this still great library.

Regards

Markus Klemm
Post by Boris Kolpackov
Hi Markus,
Raw Sourcecode below [...]
using dir_query = odb::query<directory>;
dir_query q ("ORDER BY");
switch (column)
{
q += dir_query::added;
...
}
switch (order)
{
q += "DESC";
...
}
db.query (q);
Boris
Boris Kolpackov 5 months ago
PermalinkRaw MessageReport Hi Markus,
Post by Markus Klemm
I didn't use the += operator, because it causes compiler errors
I've added the missing operator. Can you apply this patch and let
me know if there are any issues remaining:

http://scm.codesynthesis.com/?p=odb/libodb-mssql. ... 6bd96944f91912c2b174cc63f56aab

Thanks,
Boris
Markus Klemm 5 months ago
PermalinkRaw MessageReport I'd love to but:

in contrast to the 2.4.0 package, opening the project with visual studio 2013 express does not work anymore (output below), but because my shift just ends in a couple of minutes I applied the patch on the used package, e.g. used header, manually instead.

But I'm not sure if it's due that I just changed the header or if there is a problem, it didn't work. But I wanted to give a 'quick' response:

[Microsoft][SQL Server Native Client 11.0][SQL Server]Falsche Syntax in der Nõhe von ')'.
8180 (42000): [Microsoft][SQL Server Native Client 11.0][SQL Server]Anweisung(en
) konnte(n) nicht vorbereitet werden."

Translation: Wrong syntax around ')'... query could not be perpared. (I'm sorry for the german error message, microsoft ;-( )

Visual Studio 2013 express error:

C:\Users\klm\Downloads\libodb-mssql\odb\mssql\libodb-mssql-vc12.vcxproj : error : Unable to read the project file "libodb-mssql-vc12.vcxproj".
C:\Users\klm\Downloads\libodb-mssql\odb\mssql\libodb-mssql-vc12.vcxproj(173,3): The element <#text> beneath element <ItemGroup> is unrecognized.

Regards/ Mit freundlichen Grüßen

Markus Klemm

Gesendet via Mobiltelefon
Post by Boris Kolpackov
Hi Markus,
Post by Markus Klemm
I didn't use the += operator, because it causes compiler errors
I've added the missing operator. Can you apply this patch and let
http://scm.codesynthesis.com/?p=odb/libodb-mssql. ... 6bd96944f91912c2b174cc63f56aab
Thanks,
Boris
Boris Kolpackov 5 months ago
PermalinkRaw MessageReport Hi Markus,
Post by Markus Klemm
in contrast to the 2.4.0 package, opening the project with visual
studio 2013 express does not work anymore...
By applying the patch I meant applying the changes from this specific
commit to what you are currently using. Getting the whole library
from master is not a good idea for multiple reasons.
Post by Markus Klemm
Translation: Wrong syntax around ')'... query could not be perpared.
Ok, so your application now compiles fine but you get this error at
runtime. Can you enable statement tracing for the affected transaction
so that we can see the query that causes this error:

t.tracer (odb::stderr_tracer);

Boris
Markus Klemm 5 months ago
PermalinkRaw MessageReport Alright I checked out tag 2.4.0 (which differs from the downloadable 2.4.0 package btw) and cherrypicked your commit.

It broke where I want to filter the files by the directories (1:n bidirect.) and still want a particular ordering of the files.

const std::vector<decltype(directory::directory_id)> &directories
odb::query<file> filter_by_directory_query = odb::query<file>::directory->directory_id.in_range(directories.cbegin(), directories.cend());
odb::query<file> order_by_query("ORDER BY" + odb::query<file>::read_time + "ASC");
odb::query<file> final_query = filter_by_directory_query + order_by_query;

I'm sorry in advance because I guess I did something wrong/unelegant again.

Resulting SQL Query:
SELECT [files].[file_id], [files].[file_name], [files].[directory_id], [files].[
fully_uploaded], [files].[partially_uploaded], [files].[read_time], [files].[upl
oader_log] FROM [files] LEFT JOIN [directories] AS [directory_id] ON [directory_
id].[directory_id]=[files].[directory_id] WHERE [directory_id].[directory_id] IN
() ORDER BY [files].[read_time] ASC

Other querys looked fine:
SELECT [directories].[directory_id], [directories].[full_path], [directories].[f
ile_type], [directories].[active], [directories].[added], [directories].[added_b
y] FROM [directories] ORDER BY [directories].[full_path] ASC
SELECT [file_types].[type_id], [file_types].[type_description], [file_types].[md
db_importer_flag], [file_types].[search_string], [file_types].[odbc_connection_t
emplate] FROM [file_types] WHERE [file_types].[type_id]=?
SELECT [directories].[directory_id], [directories].[full_path], [directories].[f
ile_type], [directories].[active], [directories].[added], [directories].[added_b
y] FROM [directories] ORDER BY [directories].[added] ASC

SELECT [files].[file_id], [files].[file_name], [files].[directory_id], [files].[
fully_uploaded], [files].[partially_uploaded], [files].[read_time], [files].[upl
oader_log] FROM [files] WHERE [files].[file_id]=?

Regards

Markus Klemm
Markus Klemm 5 months ago
PermalinkRaw MessageReport I'm sorry this bug is not causes by your fix, it judt slipped my tests before.

Mit freundlichen Grüßen

Markus Klemm

Gesendet via Mobiltelefon

Код: Выделить всё

db->query<person> ((...) + "ORDER BY" query::age "DESC"); 
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#3 dyvniy » Вс, 7 февраля 2016, 16:43:51

Выборка определённого количество записей в SQL - LIMIT
http://myrusakov.ru/sql-limit.html
Спойлер
Запрос SQL на выборку определённого числа записей

Запрос SQL на выборку определённого числа записей
В одной из предыдущих статей, посвящённых SQL-запросу на выборку записей из таблицы, мы с Вами разобрали возможность считывания данных из таблиц. И, в принципе, этого вполне достаточно для успешного использования базы данных. Однако, иногда наиболее рационально ограничить число результирующих записей, и как раз о том, как это сделать, мы и поговорим ниже.

Давайте сразу рассмотрим запрос SQL на выборку определённого числа записей:

SELECT * FROM users WHERE id > 5 LIMIT 10
Данным запросом мы получим 10 первых записей. Все остальные отпадут. Изменение от обычного SQL-запроса на выборку данных состоит только в параметре "LIMIT". Число, которое идёт за ним, сообщает, какое количество записей мы хотим получить, и в нашем случае - это 10.

Также существует возможность задавать после "LIMIT" два числа:

SELECT * FROM users WHERE id > 5 LIMIT 10, 20
Данный SQL-запрос вернёт записи, начиная с 10-го номера включительно в количестве 20-ти штук. То есть первое число означает, с какой записи надо формировать результат выборки, а второе число означает, какое количество записей всего должно быть.

Собственно, это всё, что необходимо для выборки определённого числа записей. Используется это очень часто, например, при выводе последних 10-ти зарегистрированных пользователях. Или при выводе 5-ти свежих статей (как на главной странице моего сайта), или в других аналогичных ситуациях.
Также существует возможность задавать после "LIMIT" два числа:

Код: Выделить всё

SELECT FROM users WHERE id 5 LIMIT 1020

Данный SQL-запрос вернёт записи, начиная с 10-го номера включительно в количестве 20-ти штук.
Изображение

dyvniy M
Автор темы, Администратор
Администратор
Аватара
dyvniy M
Автор темы, Администратор
Администратор
Возраст: 41
Репутация: 1
Лояльность: 1
Сообщения: 3579
Зарегистрирован: Ср, 10 октября 2012
С нами: 11 лет 6 месяцев
Профессия: Программист
Откуда: Россия, Москва
ICQ Сайт Skype ВКонтакте

#4 dyvniy » Чт, 3 марта 2016, 14:09:43

Bucardo
https://habrahabr.ru/sandbox/65312/
она у нас используется.
Спойлер
Bucardo: Multimaster репликация
Чулан
В процессе мучений перелопатил тонну статей и решил написать подробнокомментируемый мануал. Тем более, что информации по конфигурированию multimaster и на русском языке очень мало и она какая-то кусочная.

Немного вводной. Чтобы Bucardo заработал, мы должы:
1) Сказать ему какие базы-участники на каких серверах вообще существуют.
2) Сказать ему какие таблицы участвуют в репликации.
Внимание: если разработчики добавят в приложение новую таблицу, мы должны об этом сообщить bucardo.
Тоже самое касается изменения схемы существующих таблиц.
3) Сказать ему какие группы таблиц существуют и какие таблицы попадают в какие группы. Группы нужны на тот случай, если между разными серверами надо реплицировать разные таблицы. Удобнее работать с группой, чем каждую отдельно указывать (очень похоже на группы в Nagios).
4) Сказать ему какие группы баз данных существуют. Цель — та же, что и для таблиц.

Перейдем к установке. Вариант для Debian 7.

Подразумевается, что пакеты postgresql-9.1 и postgresql-client-9.1 уже установлены.

Предварительная подготовка.

Серверы будут называться node1 и node2.

Обязательно также проверить, чтобы все участвующие PostreSQL-серверы слушали внешние интерфейсы:
# netstat -plnt4 | grep 5432
tcp 0 0 0.0.0.0:5432 0.0.0.0:* LISTEN 12345/postgres


Устанавливаем пакет Bucardo и поддержку PL/Perl для PostgreSQL на каждом из серверов:
# apt-get install bucardo postgresql-plperl-9.1


Активируем на каждом из серверов:
# sed -i 's/ENABLED=0/ENABLED=1/' /etc/default/bucardo


Мейнтенеры пакета почему-то не догадались создать директорию под PID, поэтому создадим ее сами на каждом из серверов:
# mkdir /var/run/bucardo


Удостоверяемся, что мы можем подключиться через TCP-сокет к СУБД на каждом из серверов:
# psql -U postgres -h 127.0.0.1


Если не помните пароль, то простейшая инструкция здесь.
Если PG не хочет принимать запросы с конкретного адреса конкретного пользователя, то настройте /etc/postgresql/9.1/main/pg_hba.conf

Далее будет происходить инициализация базы. Она будет создана пользователем postgres, но наполнена пользователем bucardo, поэтому можно упереться в проблему подключения.
Дабы ее избежать, заранее внесем строку для него в /etc/postgresql/9.1/main/pg_hba.conf. Кроме того, уже в процессе работы Bucardo будет обращаться не только к своей ноде кластера, но и к парной. Поэтому ее тоже не забываем. Если у Вас в кластере серверов больше, то не забудьте о них. На каждом из серверов:
host all bucardo 127.0.0.1/32 trust
host all bucardo SECOND.NODE.IP.ADDRESS/32 password


После этого рестартанем СУБД:
# pg_ctlcluster 9.1 main restart


Установка Bucardo.

Утилита bucardo_ctl в последних версиях Debian была заменена на bucardo, поэтому мы будем использовать ее.
Инициализируем базу данных:
# bucardo install


Диалог выглядит примерно так:
# bucardo install
This will install the bucardo database into an existing Postgres cluster.
Postgres must have been compiled with Perl support,
and you must connect as a superuser

Current connection settings:
1. Host: localhost
2. Port: 5432
3. User: postgres
4. Database: postgres
5. PID directory: /var/run/bucardo
Enter a number to change it, P to proceed, or Q to quit: P

Password for user postgres:
Postgres version is: 9.1
Password for user postgres:
Creating superuser 'bucardo'
Password for user postgres:
Attempting to create and populate the bucardo database and schema
Password for user postgres:
Database creation is complete

Updated configuration setting "piddir"
Installation is now complete.
If you see errors or need help, please email bucardo-general@bucardo.org

You may want to check over the configuration variables next, by running:
bucardo show all
Change any setting by using: bucardo set foo=bar


В процесс инициализации база была создана из файла /usr/share/bucardo/bucardo.schema, поэтому нет необходимости ее заполнять руками, как это описано в мануалах прошлых версий.

Bucardo установлен, можно его запустить:
# bucardo start


Настройка репликации.

Прежде, чем настроить репликацию, создадим тестовые базы, которые будем реплицировать.
На каждом из серверов:
# psql -U postgres -c "CREATE DATABASE mydb;"
# psql -U postgres mydb -c "CREATE TABLE mytable ( num123 integer PRIMARY KEY, abc varchar(10) );"


Еще один важный момент касательно безопасности. После добавления реплицируемой базы в настройку, Bucardo впишет пароль пользователя в базу. А так как при установке он его не запросил, то сделал его точно таким же, как у пользователя postgres. Другими словами у нас в базе bucardo будет храниться в открытом виде пароль от суперпользователя, что несколько опасно.
Поэтому сделаем ему другой пароль. На каждом из серверов:
# psql -U postgres -c "ALTER USER bucardo WITH PASSWORD 'eiP4uSash5';"


Далее даем информацию Bucardo, как подключиться к базе данных, которую будем реплицировать. Я не поклонник Unix-сокетов в условиях высокой нагрузки (отдельная тема для разговора), поэтому даже там, где локально, укажем TCP-сокет.

ВНИМАНИЕ: Это мы делаем на сервере node1. И вообще далее работаем только с node1 пока не уточнено, что надо делать на обоих.

Добавим локальную (mydb_node1) и ее удаленную копию (mydb_node2) с сервера node2:
# bucardo add database mydb_node1 dbname=mydb dbhost=127.0.0.1 dbuser=bucardo dbpass=eiP4uSash5
Added database "mydb_node1"

# bucardo add database mydb_node2 dbname=mydb dbhost=node2.example.com dbuser=bucardo dbpass=eiP4uSash5
Added database "mydb_node2"
Здесь:
mydb_nodeX — внутренее обозначение базы. Это имя Bucardo использует во внутренних работах с базой.
dbname=mydb — реальное имя базы в PostgreSQL, на которое ссылается mydb_nodeX.
dbuser=bucardo — под кем Bucardo будет подключаться к СУБД, чтобы работать с этой базой.

Результат мы можем видеть так:
# bucardo list database
Database: mydb_node1 Status: active Conn: psql -p -U bucardo -d mydb -h 127.0.0.1
Database: mydb_node2 Status: active Conn: psql -p -U bucardo -d mydb -h node2.example.com


Эти настройки берутся из таблицы db базы bucardo, где и сидит упомянутый выше пароль:
# psql -U postgres bucardo -c "SELECT name,dbname,dbhost,dbuser,dbpass,status FROM db;"
name | dbname | dbhost | dbuser | dbpass | status
------------+--------+-------------------+---------+------------+--------
mydb_node1 | mydb | 127.0.0.1 | bucardo | eiP4uSash5 | active
mydb_node2 | mydb | node2b.forbet.net | bucardo | eiP4uSash5 | active
(2 rows)


Теперь нам надо добавить таблицу, которую мы будем между ними реплицировать. В большинстве случаев люди реплицируют целиком базу, поэтому уж сразу все добавим (группа таблиц(herd) создастся автоматически). Если разработчики придумают новую таблицу, мы просто добавим ее потом в группу и все само заработает — так как дальнейшие настройки будут касаться группы целиком.
# bucardo add table all --db=mydb_node1 --herd=mydb_herd
Creating herd: mydb_herd
Added table public.mytable to herd mydb_herd
New tables added: 1
Здесь:
--herd=mydb_herd — имя группы таблиц, чтобы потом настраивать синхронизацию не к каждой отдельно, а всем скопом.

И сразу можем ее посмотреть:
# bucardo list tables
1. Table: public.mytable DB: mydb_node1 PK: num123 (int4)
Здесь нужно заострить внимание на PK. Bucardo, похоже, не работает с таблицами без первичных ключей. Вы потом не сможете синк сделать.

И группу тоже видно:
# bucardo list herd
Herd: mydb_herd DB: mydb_node1 Members: public.mytable


Тоже самое касается последовательностей. В нашем примере их нет, но вдруг кто использует. Группу под них свою создавать не будем, чтобы не усложнять. Вероятность того, что таблицы реплицируются в одном направлении, а последовательности в другом — чрезвычайно мала. Поэтому пусть будет одна группа для таблиц и последовательностей:
# bucardo add sequence all --db=mydb_node1 --herd=mydb_herd
Sorry, no sequences were found
New sequences added: 0


Следующая наша задача создать репликационную группу. В этой группе мы скажем какая база будет источником, а какая реципиентом данных. Создадим сначала саму группу, пока пустую:
# bucardo add dbgoup other_mydb_servers
Created database group "mydb_servers_group"


Добавляем оба наших сервера в группу, указывая кто какую роль будет исполнять. Это единственная точка, где отличаеся настройка master-slave от master-master.
Изначально можно подумать, что source — это источник, а target — это реципиент. На самом деле это не совсем так. source — это тот, кто работает и как источник и как реципиент, а target — только реципиент.
То есть если у нас master-slave, то указываем одного source, а второго target. А если у нас master-master, то оба будут source, а target'ов не будет вообще.

Вариант для MASTER-->SLAVE:

# bucardo add dbgroup mydb_servers_group mydb_node1:source
Added database "mydb_node1" to group "mydb_servers_group" as source

# bucardo add dbgroup mydb_servers_group mydb_node2:target
Added database "mydb_node2" to group "mydb_servers_group" as target


Вариант для MASTER<-->MASTER:

# bucardo add dbgroup mydb_servers_group mydb_node1:source
Added database "mydb_node1" to group "mydb_servers_group" as source

# bucardo add dbgroup mydb_servers_group mydb_node2:source
Added database "mydb_node2" to group "mydb_servers_group" as source


Все! У нас написано какие есть базы. Написано какие есть в них таблицы. Написано кто в какой группе. Осталось сказать заключительный штрих — сказать какая группа таблиц будет «курсировть» между базами какой группы. Другими словами — создать «синк»:
# bucardo add sync mydb_sync herd=mydb_herd dbs=mydb_servers_group
Added sync "mydb_sync"


Можем посмотреть, что у нас получилось:
# bucardo list sync
Sync: mydb_sync Herd: mydb_herd [Active]
DB group mydb_servers_group: mydb_node1 (source) mydb_node2 (source или target - как настроили)


После изменения настроек обязательно рестартовать Bucardo:
# bucardo restart


========

Проверка:

на первой ноде node1 запускаем:
# psql -U postgres mydb -c "INSERT INTO mytable VALUES (1, 'a');"


а на второй node2 проверяем:
# psql -U postgres mydb -c "SELECT * FROM mytable;"


кто сделал multimaster, тому надо и в обратном направлении проверять. Создаете на node2, а проверяете на node1.

========

Вопросы, которые возникнут у большинства людей:
1) Что будет с таблицей на target-базе, если таблица на source-базе была изменена пока Bucardo был выключен или сеть была недоступна?

Ответ: все Ok. При старте или при появлении сети Bucardo передаст данные на target-сервер. Так что target-сервер может ломаться как угодно. Единственное требование — на нем должна быть та же схема данных (структура таблиц), что и на первом.
__
2) Если база большая (десятки-сотни гигабайт), Bucardo «отламывается» и не синхронизирует до конца. Как быть?

Ответ: переведите sync в состояние неактивного. Но Bucardo должен быть включен для source-базы для логивания запросов.
bucardo update sync mydb_sync status=inactive (для multimaster на всех нодах)
Далее делаете pg_dump/pg_restore руками и возвращаете синк в активный режим (для multimaster сначала на той, куда шли новые запросы после запуска дампа).
Изображение


Название раздела: Программирование (под Desktop и Android)
Описание: Разработка и отладка приложений. Упор на 3D-графику.

Быстрый ответ


Введите код в точности так, как вы его видите. Регистр символов не имеет значения.
Код подтверждения
:) ;) :hihi: :P :hah: :haha: :angel: :( :st: :_( :cool: 8-| :beee: :ham: :rrr: :grr: :* :secret: :stupid: :music: Ещё смайлики…
   

Вернуться в «Программирование (под Desktop и Android)»

Кто сейчас на форуме (по активности за 15 минут)

Сейчас этот раздел просматривают: 8 гостей