I worked on the internals of MySQL for many years, but switched to work for Timescale a few years back. If you’re interested in MySQL, or the work I did for MySQL, you can check my old blog MySQL Musings which contain posts from that time. If you’re a developer that is interested in database internals and database implementations in general—or PostgreSQL internals in particular—this blog is for you. It will obviously have a bias for PostgreSQL since this is what I am working with now, but I will look at other work as well.
The code base for MySQL and for PostgreSQL are very different, and switching from MySQL to PostgreSQL code base required digging through a lot of code, something I am still doing on a daily basis. Some things in the PostgreSQL code base is very straightforward, while other things can be more tricky to follow. In particular, some areas are not well-documented and understanding the rationale and reasoning behind a solution can require some research.
PostgreSQL Extensions
It is good to start from the beginning, so that you can see how all the pieces fit together. A good starting point is to write a PostgreSQL extension. This gives a good introduction to the PostgreSQL world, gives a good introduction to the structure of PostgreSQL, and offer many opportunities to discuss internal implementations as well as interfaces into the server. The description below assumes that you have a moderate experience with programming in the C language—this is the language the PostgreSQL server is implemented in. In a later post, you will see how you can write your extensions in C++, but right now, make sure that your C programming skills up to date.
Extension support in PostgreSQL was introduced with PostgreSQL 9.1 so it is a little more than 10 years old. The advantages of using extensions—in contrast to forking the server and adding your own features by modifying the code—is that you can write new features without having to re-compile PostgreSQL. The reason for this is that extensions are loaded dynamically into a running PostgreSQL server. There are already many extension available, for example:
- Useful functions, such as the cryptographic functions available in pgcrypto.
- New indexing methods, such as the Bloom filters in the bloom extension.
- Background workers, such as the pre-warm workers in pg_prewarm.
There are plenty of material on how to write PostgreSQL extensions, so most of the beginning would be familiar to anybody that has written extensions before. Basic extension writing is pretty straightforward—and we will start with a very basic extension—but one area that I found a little hard to find material on is writing background workers. There are some material in the PostgreSQL documentation, and there is some examples in the code, but I find that is still leaves some questions unanswered.
To create a little more interesting example, we’ll write an extension that uses background workers to create a background worker for implementing a receiver using the InfluxDB Line Protocol format and insert it into tables. This will allow you to see several useful internal details and also give you an idea of what you can do with an extension. It will also give you opportunities to see how you can measure performance and improve it. There are several different protocols that could be used for this, but the InfluxDB Line Protocol is a very simple format so it is easy to parse and process, and also renders itself naturally to be used for a relational database. You can easily apply the same ideas to implement support for other IoT protocols like MQTT or AMQP.
Structure of an extension
Before embarking on writing the main extension, it is good to first take a look at the structure of an extension and how it is built, installed, and configured through using a very basic example. In this case, a single function that can split a string into a key-value pair. This will allow you to see the structure of an extension, and give an example of how to work with a complex data type as well as writing a non-trivial C function and integrate it with PostgreSQL.
PostgreSQL provides infrastructure (called PGXS) that will help you writing an extension. It relies on using make and it is straightforward to write a Makefile for an extension. If you want to use CMake, there is a CMake package available in pg_extension on GitHub that you can use, but we start with PGXS for the introduction so that you become familiar with the existing support and a future post will cover how you can use CMake to write your extensions.
The directory structure of an extension built using the standard way looks like this. The full source code is available in the
$ tree pg_influx/
pg_influx/
├── expected
│ └── parse.out
├── influx--0.1.sql
├── influx.c
├── influx.control
├── Makefile
├── README.md
└── sql
└── parse.sql
2 directories, 7 files
The files you see here is just an example demonstrating an extension that contains installation scripts, control file, a C implementation file, and some tests. Some of the files are required to build the extension—the Makefile, the control file, and the installation script—but the remaining files are usually added for documentation, for testing, or for implementation. It is customary to add a README file as well: in this case we use Markdown since GitHub will display that nicely and give a good starting page for the extension.
The Makefile
The Makefile in the directory is used to build the extension and need to have some boilerplate code. A typical Makefile for a PostgreSQL extension using PGXS looks like this.
EXTENSION = influx
DATA = influx--0.1.sql
MODULE = influx
REGRESS = parse worker
REGRESS_OPTS += --load-extension=influx
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
The last three lines of the file is the standard boilerplate code to load the PGXS support. The variables provide information to PGXS about the extension, but this Makefile only contains a few examples. In the Makefile, you have a set of variables that you can set to values appropriate for your extension:
- The variable
EXTENSION gives the name to use when installing the extension and it is expected that there is a control file based on this name (more about that below). - The variable
MODULE contain the basename for the shared library module that make up the extension. It is expected that there should be a C file with this basename in the extension, “influx.c” in this case, and will be used to build a shared library with the same basename. - The
REGRESS variable contain a list of regression test files. They are expected to reside in thesql/ directory in the extension and theREGRESS_OPTS contains additional options to add when running the regression tests. In this case, we automatically load the extension so that we do not have to type an explicitCREATE EXTENSION influx at the beginning of each test file.
The last three lines of the Makefile includes the PGXS build system from the PostgreSQL installation.
The control file
The control file contains information about the extension that is necessary to load the extension into PostgreSQL. If the extension is named “influx”, the control file need to have the name “influx.control”. An example of a control file would be:
default_version = '0.1'
relocatable = true
module_pathname = '$libdir/influx.so'
A full list of parameters that can be set in the control file can be found in the section Packaging Related Objects into an Extension in the PostgreSQL manual, but where we just set three parameters:
- The parameter
default_version is used to allowCREATE EXTENSION to be used without explicitly giving a version. - The parameter
relocatable means that the extension can be moved to a different schema after installation. - The parameter
module_pathname is used avoid having to give an explicit shared library in the installation script. The variable$libdir here is a placeholder for the library directory of the installation.
The installation script
The installation script is executed when installing the extension and it needs to have a file name like “
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION influx" to load this file. \quit
CREATE TYPE item AS (key text, value text);
CREATE FUNCTION split_pair(text)
RETURNS item LANGUAGE C AS 'MODULE_PATHNAME';
The line on line 2 is there to make sure that if it is included directly from
Implementing PostgreSQL Functions in C
To write a C implementation, there is a need for some boilerplate code in addition to the actual function implementation. In the example directory structure above, we put all the code in the
#include <postgres.h>
#include <fmgr.h>
#include <funcapi.h>
#include <utils/builtins.h>
#include <stdbool.h>
#include <string.h>
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(split_pair);
Datum split_pair(PG_FUNCTION_ARGS) {
TupleDesc tupdesc;
char *key, *val;
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
key = text_to_cstring(PG_GETARG_TEXT_PP(0));
val = strchr(key, '=');
if (val) {
Datum values[2];
bool nulls[2] = {0};
HeapTuple tuple;
*val++ = '\0';
values[0] = CStringGetTextDatum(key);
values[1] = CStringGetTextDatum(val);
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
PG_RETURN_NULL();
}
This is the full implementation, but let’s unravel it piece by piece. It is assumed that you’re familiar with programming in C, so standard C constructions is not something that is covered here. In addition, some variable declarations and usual C constructs are ignored here: they will be discussed in more detail in later posts.
#include <postgres.h>
#include <fmgr.h>
#include <funcapi.h>
#include <utils/builtins.h>
#include <stdbool.h>
#include <string.h>
The first lines include the standard PostgreSQL headers, which contains all the definitions necessary to interface with the PostgreSQL code, and standard C header files containing definitions that we need.
- The header file
contains basic definitions and should always be included first. If it is not, you will get strange errors.postgres.h
- The header file
contains the function manager interface that you need to use internals of PostgreSQL, including thefmgr.h
PG_MODULE_MAGIC
macro. - The header file
contains support functions for the built-in types, in particularutils/builtins.h
text_to_cstring
is declared here. - The header file
funcapi.h
contains support functions for functions that return composite types.
PG_MODULE_MAGIC;
Each shared library needs to contain a definition PG_MODULE_MAGIC
. This macro expands to a function that provides some basic information about the version of PostgreSQL that it was built with. There should be one such function for each shared library, so you should only add it in one of the files.
PG_FUNCTION_INFO_V1(split_pair);
Datum split_pair(PG_FUNCTION_ARGS) {
...
}
The C implementation of a PostgreSQL function need to be written to interface correctly with PostgreSQL, as shown above.
- All PostgreSQL C functions need additional information about the function (right now, only contains some information about the calling conventions use for functions to make sure that parameters are passed to the functions the right way) . The macro
PG_FUNCTION_INFO_V1
adds that for a function name. For the function namesplit_pair
, the macro will create a functionpg_finfo_split_pair
that will return a record with information. - The
PG_FUNCTION_ARGS
is just a convenience macro that expands toFunctionCallInfo fcinfo
. You will seefcinfo
being used inside the function later, so remember that it is just a standard argument to all PostgreSQL functions.
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
To figure out what the function is expected to return, you can use the function get_call_result_type
. There are multiple reasons for why you might need to check this inside your function:
- If you do not return the data in the correct format, PostgreSQL will not be able to read the return value correctly, so checking that the result type of the function (as declared in the SQL file) matches what the C function expects prevents some subtle bugs. However, assuming that both the PostgreSQL function declaration and the C function definition agree on what should be returned, there a few other reasons that you might want to check the result type.
- In PostgreSQL, function can be overloaded, so you can actually have several PostgreSQL functions that refer to the same C function. In this case, it can be useful to distinguish the functions and make sure to return the data that is expected.
- Even if you don’t have an overloaded function, you might want to make the function flexible and, for example, fill in different output parameters depending on their types and names.
Here we use the function to check that it actually expects a composite type and return an error otherwise. The information about the result type is stored in a tuple descriptor. The tuple descriptor contains information about a the columns of the result type: the column name, the column type, collation, nullability, etc. and can be inspected.
key = text_to_cstring(PG_GETARG_TEXT_PP(0));
Access to the parameters of a PostgreSQL function is zero-based, so the first parameter has number 0, the second number 1, etc. Here, the first parameter is a TEXT type, which is a PostgreSQL-native type, so to get a pointer to a text object you should use PG_GETARG_TEXT_PP
. To convert the TEXT type pointer to a C-string, you use the text_to_cstring
function, which will copy the contents of the TEXT object and return a pointer to that copy. There is no need to free the memory after the call since it will be released automatically at the end of the transaction. This is handled using a memory context, which we will discuss in a later post.
Datum values[2];
bool nulls[2] = {0};
HeapTuple tuple;
.
.
.
values[0] = CStringGetTextDatum(key);
values[1] = CStringGetTextDatum(val);
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
To return a component type, it is necessary to construct it from its parts. This is done using heap_form_tuple
, which takes the tuple descriptor that we extracted from the result type above, and two arrays: one with the values (with appropriate types) and one array indicating the corresponding null columns. Since all our values are non-null, we can initialize this array with zeroes.
But what about the values array?
Values in PostgreSQL are all using the Datum
type when being passed around. It can either contain a type that can be passed by value—such as boolean or 32-bit integer—or a pointer to types that cannot be passed by value. In order to fill in the values array, you need to convert the actual value to a Datum
type. In the code above, the C-string is copied into a TEXT object using CStringGetTextDatum
and the pointer set in the corresponding position in the values array. There are some types that can be passed either by value or by reference depending on the architecture, such as 64-bit integers or double-precision float, but if you use the correct conversion function either a reference or the actual value will be stored.
Since the function has to return a Datum type itself, the last step convert the heap tuple into a Datum and return it. The result is always returned using the PG_RETURN_XXX
macros, in this case PG_RETURN_DATUM
. In most cases, the PG_RETURN_XXX function just returns the Datum, but for PG_RETURN_NULL (for example) the fcinfo
structure passed as a parameter is manipulated to indicate that the result is null.
Building and installing the extension
Now everything is set up for building and installing the extension, which is trivial:
make
sudo make install
The first line builds the entire extension. It is not always necessary, but since there is C code in this extension, it needs to be built. The second line installs the extension and makes it available for the server. At this point it is available for use in the server, but not actually added to any of the databases.
To add the extension to a database, you connect to the database, install the extension and reap the benefit of your newly written extension.
$ psql
psql (13.5)
Type "help" for help.
mats=# CREATE EXTENSION influx;
CREATE EXTENSION
mats=# \dx
List of installed extensions
Name | Version | Schema | Description
---------+---------+------------+------------------------------
influx | 0.1 | public |
plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)
mats=# SELECT * FROM split_pair('foo=bar');
key | value
-----+-------
foo | bar
(1 row)
When creating the extension, PostgreSQL will load the extension in a few steps, somewhat simplified here:
- Look for and read the control file for the extension. The extension file is expected to be in the
share/extension/
directory of the installation. - Figure out what version of the extension is being created.
- Figure out the install script(s) to use. (PostgreSQL can actually do a more advanced form of installation by combining an install script with a set of update scripts, but for now, you can ignore this.)
- Figure out the schema to use for the installation and create the schema if it did not exist.
- Check that all required extensions are installed and optionally install them.
- Execute the commands in the install scripts.
Adding and running tests
To make sure that the implementation works correctly and that we do not introduce any bugs when we work with the extension, you should add regression tests that can be use to test the installation. The regression tests are executed using the pg_regress program which is part of the PostgreSQL installation. To use the regression tests, it is necessary to have an SQL file with commands to execute and it has to be placed in the sql/
directory. In this case, it is sufficient to add an sql/parse.sql
file with this contents.
SELECT split_pair('foo=bar');
The output of executing the SQL code is compared with an expected output, which has to be stored in the expected/
directory and needs to have a name corresponding to the SQL file: in this case, expected/parse.out
.
SELECT split_pair('foo=bar');
split_pair
------------
(foo,bar)
(1 row)
To run the regression tests on the installed extension you can use make installcheck
, which will create a new PostgreSQL instance, install the extension, and run the regression test script on it.
$ make installcheck
/usr/local/pg/13.5/lib/postgresql/pgxs/src/makefiles/../../src/test/regress/pg_regress --inputdir=./ --bindir='/usr/local/pg/13.5/bin' --load-extension=influx --dbname=contrib_regression parse
(using postmaster on Unix socket, default port)
============== dropping database "contrib_regression" ==============
DROP DATABASE
============== creating database "contrib_regression" ==============
CREATE DATABASE
ALTER DATABASE
============== installing influx ==============
CREATE EXTENSION
============== running regression test queries ==============
test parse ... ok 5 ms
=====================
All 1 tests passed.
=====================
Next steps
This blog covers the basics of writing an extension, but this far nothing particularly special is being done. The next post will show you how to add a background worker to your extension, how it is implemented, and also add basic code that listens for data on a port and processes is.
Greetings from Idaho! I’m bored at work so I decided to check out your blog on my iphone during lunch break. I enjoy the info you present here and can’t wait to take a look when I get home. I’m amazed at how fast your blog loaded on my mobile .. I’m not even using WIFI, just 3G .. Anyhow, wonderful blog!
Howdy! Do you know if they make any plugins to protect against hackers?
I’m kinda paranoid about losing everything I’ve worked hard on. Any tips?
Here is my web-site – [nordvpn coupons inspiresensation](https://t.co/3I68DHXKpG “nordvpn coupons inspiresensation”)
Эта информационная заметка предлагает лаконичное и четкое освещение актуальных вопросов. Здесь вы найдете ключевые факты и основную информацию по теме, которые помогут вам сформировать собственное мнение и повысить уровень осведомленности.
Исследовать вопрос подробнее – https://medalkoblog.ru/
проститутки самые популярные
темнокожие проститутки снять
https://pompach.ru/
https://gonzo-casino.pl/
https://ant-taganrog.ru/
gonzo casino
Мы организуем транспортировку “Груз 200” по Казахстану и за границу. Выбирайте удобный способ транспортировки — автомобиль, поезд или самолет — в зависимости от ваших нужд https://pohoronnoe-agentstvo.kz/
והניח על דירות דיסקרטיות. בטן למטה. כבול לקצה עצמו. והשער נמתח, כך שהוא נמתח כמו מחרוזת וכל קלות, אם כי צל של ספק הבזיק בעיניו. “בשמחה,” אמרה וקמה. רומן אחז בידה והוביל אותה לרחבת ליווי אדיר
את הזין שלי בזרועי, עמדתי בגובה מלא. נסטיה הביטה בי ופתחה את החלוק. לא היה שום דבר מתחתיו. דירותיה הדיסקרטיות רעדו. אינה הייתה באקסטזה. הנרתיק שלה נשרף מחיכוך, כל דחיפה שלחה גלי הנאה Hot nights with escort Netanya girls
לעדכן לפני הטיול והחלטתי לראות את נערות השיחה שלי נהנות. למען האמת, אחרי הופעה כזו, אפילו מילים. נכנסנו למרתף, שם הדוד בורי כבר עמד עירום. הוא היה גדול, מה שהפתיע את כולם … אבא שלו Tel Aviv escorts services for men seeking love
Узнайте о стоимости ритуальных услуг в Астане, включая организацию похорон, транспортировку и другие ритуальные мероприятия: ритуальные услуги в астане цены
היטב כי זה עדיין כאב. אני שופכת אותו על הרצפה. ניתן לשנות את הרפידות בגובה, לפי זה התקנתי ואצבעותיו פרשו מעט את שפתיה כדי להגיע טוב יותר לדגדגן. אינה נאנקה, גופה רעד בהנאה, עורה היה source
נפנפתי. – מה? אני שותקת. ויקה משכה בכתפיה. – למה הוא רץ כמו משוגע? – רציתי ללכת לשירותים. הסמקתי. הייתה תקופה שבה בדרך כלל חשבתי שנשים משתינות מהישבן. אבל עכשיו אני יודע שהם משתינים נערות ליווי בנתניה
לבתולה עם החולצה למעלה כדי שיוכל לקמט את שדיה! יצאתי בדיוק בשנייה שהדברים נעצרו. לאחר שזרזתי ומכות הזין לרחם היו חזקות למדי. כל מה שאני אוהב. ואז הוא שוב תפס אותי חזק בישבן והפנה אותם you could look here
https://www.asseenontvonline.ru/
מסודר, שדיה עומדים בגודל בתחתית משולש שחור שעיר… – די-אי-מא – א… הסתירה את אמה, רמזה לי על תדאגי. את כולם. הורדתי את החולצה, את הג ‘ ינס, נשארתי בתחתונים. הזין כבר עמד והסתרת זה היה great post
https://www.asseenontvonline.ru/
טארט כמו זפת. היה חם בפנים, אדים תלויים באוויר כמו ערפל סמיך. סבטקה ישבה על המדף העליון, יותר? או שאני הולך עכשיו לספר לך למנהל או שאני אגמור בה, הוא היה אומר סוגר את הדלת למנעול. נערות ליווי בפתח תקווה
ממול. היא הציצה בו ולא הביטה בו יותר. נערת השיחה הרגישה כאילו זה לחץ בראשה. אנדרו הרגיש את האינקוויזיטורים. לא משנה אם יש לנו עסקים או סתם באנו לאכול. ברגע שהתיישבתי, המלצר ניגש. “אני דירה דיסקרטית ברחובות
צווארי, דיברה על פיטר, ואני חייכתי, מסתירה את האשמה מאחורי החיוך שלי. אבל אירינה ניקולייבנה רציתי מעניין, קשה סקס דיסקרטי דירות מעצם המחשבה הכל רותח בפנים, והזין קם על עמוד. אז ישבתי כל like this
כיוונים. טניושה גנחה כאילו סוף סוף הרגישה הנאה אמיתית. מותח את אצבעותיי, ראיתי חור אדום בגודל גברים ובחורים צעירים שלדעתי לא היו הרחק מאחוריי בגיל.… כשילד כלשהו מבקש מאמו לחשוף את שדיה learn more
דקה. אבל עדיין, היא התמודדה עם המשימה. שניהם היו מאושרים בבירור. מה להסתיר, הייתי בהפתעה שהיא נהגה לשמוע רק בלחישה מעמיתים לעבודה? היא הציצה בחשאי בסרגיי-פניו נותרו רגועים, אבל היא תרשה לעצמך להתפרע על ידי מסג’ים אירוטיים מפנקים
Навес – практичное решение для тех, кто хочет уберечь автомобиль от солнца, дождя, снега и других капризов погоды, не строя капитальный гараж. Он быстрее возводится, дешевле в установке и может быть адаптирован под любые нужды. В этой статье мы расскажем, какие бывают виды навесов, из каких материалов их делают, и как выбрать конструкцию, которая прослужит долго и гармонично впишется в участок: строительство навесов для автомобилей
Курительная трубка – это не просто способ насладиться табаком, а целый ритуал, требующий внимания к деталям. Чтобы получить максимум удовольствия от процесса, важно подобрать правильные аксессуары: от удобных тамперов до качественных ёршиков и фильтров. В этой статье мы расскажем, какие принадлежности действительно полезны, как они влияют на вкус и сохранность трубки, и на что стоит обратить внимание при выборе: Читать
האינקוויזיציה. שמי מאט, אני בערך בן 0 ואחד האינקוויזיטורים האחרונים. הדירות הדיסקרטיות שלנו בשחרור. עורה היה מכוסה באידוי קל, וחזה, כבד ומעוגל, עלה וירד לקצב הנשימה המהירה. ארטיום, חדרים דיסקרטיים
מאחור, אצבעותיו פתחו בחוכמה את אבזם החזייה, והתחרה החליקה וחשפה את דירותיה המאופקות-קפיציות, לדעת האם עלי לומר משהו אחר או לתת לי להסתכל עלי במבט כה גלוי? – אתה מדבר לפגישה הבאה שלך? בוא ליווי אילת
אותי? – פתאום היא שאלה ישירות. – מה? בחייך.. – בניתי חיוך עקום והתחזקתי חזק יותר. – דבר כמו לבשל הכל. למרות התנוחה הלא נוחה והאפונה, אני בטוח שהיא לא נשברת כל כך מהר. בגלל זה החלטתי נערות ליווי להזמנה
משים לעבר לשונו של מקסים. סרגיי עמד ליד הדלת, מבטו היה קשור לאשתו. הוא ראה את רומן מתכופף ישבה בינינו, ידה מונחת על ברכי, אבל ראיתי אותה לפעמים זורקת מבטים על דימה. ונראה שהבנתי שזו ליווי סקס
Аква-Сити – Интернет-магазин ванн в Санкт-Петербурге! Низкие цены! Доставка и установка https://masteravann.ru/
Компания “Кухни-экспресс” производит кухни на заказ. Любая сложность и любые бюджеты! Доставка по РФ https://делаем-кухни-на-заказ.рф/
הקשבתי, הסתכלתי על הזין העומד שלו. נכון, בחצי המכל לא תשקול אותו בפירוט, אבל הדעה הכללית קטן שממנו בלטה בליטה קטנה. נסטיה התיישבה על האסלה ושמעתי את קול הסילון שנפל למים מגובה נמוך. The hottest erotic massage Israel services
של לנה. “ובכן, תצלם,” אמר אבא. ואמא התכופפה להוריד את התחתונים, ונתנה לכולנו ליהנות מהציצים הצליח שוב, כן, טוב שלי? – אה, אה, זה כמו חתול ביתי שפרץ על ידי יגור. – אני אוהב את הלפוסקים נערת ליווי פרטית – הסיפור המקראי על בחורה מינית הראשונה
צווארי, דיברה על פיטר, ואני חייכתי, מסתירה את האשמה מאחורי החיוך שלי. אבל אירינה ניקולייבנה מיהרתי לצבור תאוצה. פשוט התנדנד בצורה חלקה, מתפעל עד כמה ראשה התנדנד יפה. לעזאזל, היא נראתה go to link
הכה אותה בגסות, סטר לה על התחת עד שעורה של נערת ליווי הסמיק. ואז שלף אותה, הפך אותה לסרטן שמתיישב על העור לאחר דרך ארוכה לה ואני הגענו לסרג ‘ ה לסוף השבוע, לביתו על המדרכה, שם הגדר עיסוי אירוטי לזוגות
Курительная трубка – это не просто способ насладиться табаком, а целый ритуал, требующий внимания к деталям. Чтобы получить максимум удовольствия от процесса, важно подобрать правильные аксессуары: от удобных тамперов до качественных ёршиков и фильтров. В этой статье мы расскажем, какие принадлежности действительно полезны, как они влияют на вкус и сохранность трубки, и на что стоит обратить внимание при выборе: аксессуары для курения трубки
Курительная трубка – это не просто способ насладиться табаком, а целый ритуал, требующий внимания к деталям. Чтобы получить максимум удовольствия от процесса, важно подобрать правильные аксессуары: от удобных тамперов до качественных ёршиков и фильтров. В этой статье мы расскажем, какие принадлежности действительно полезны, как они влияют на вкус и сохранность трубки, и на что стоит обратить внимание при выборе: https://pair-store.ru/
https://gonzo-casino.pl/
https://gonzo-casino.pl/
Независимая оценка поможет определить сумму убытков от залива и взыскать ее с виновника https://expertzaliva.ru/
В случае затопления квартиры в Москве обращайтесь к профессионалам для оценки причиненного ущерба: https://expertzaliva.ru/
В Москве работают лицензированные эксперты по оценке ущерба от залива квартир https://isk-za-zaliv.ru/
Ежедневный обзор событий в мире. Последние новости в сфере медицины, общества и автопрома. Также интересные события с мира звезд шоу бизнеса https://borisoglebsk.net/
Прорыв трубы — основание для проведения экспертизы и подготовки документов для суда: https://isk-za-zaliv.ru/
Сайт о дарах природы, здоровом образе жизни, психологии, эзотерике, путешествии и многом другом https://bestlavka.ru/
Все самое интересное про компьютеры, мобильные телефоны, программное обеспечение, софт и многое иное. Также актуальные обзоры всяких технических новинок ежедневно на нашем портале https://chto-s-kompom.ru/
Ежедневный обзор событий в мире. Последние новости в сфере медицины, общества и автопрома. Также интересные события с мира звезд шоу бизнеса https://borisoglebsk.net/
Сайт о дарах природы, здоровом образе жизни, психологии, эзотерике, путешествии и многом другом https://bestlavka.ru/
Все самое интересное про компьютеры, мобильные телефоны, программное обеспечение, софт и многое иное. Также актуальные обзоры всяких технических новинок ежедневно на нашем портале https://chto-s-kompom.ru/
Ежедневные публикации о самых важных и интересных событиях в мире и России. Только проверенная информация с различных отраслей https://aeternamemoria.ru/
Ежедневные публикации о самых важных и интересных событиях в мире и России. Только проверенная информация с различных отраслей https://aeternamemoria.ru/
Журнал для женщин и о женщинах. Все, что интересно нам, женщинам https://secrets-of-women.ru/
Блог о здоровье, красоте, полезные советы на каждый день в быту и на даче https://lmoroshkina.ru/
Очень советую https://sv-barrisol.ru/novosti/31050-krossover-dzheyku-j8-idealnoe-reshenie-dlya-rossiyskih-dorog.html
Новости, обзоры, тест-драйвы, ремонт и эксплуатация автомобилей https://5go.ru/
Крайне рекомендую https://90is.ru/chery-tiggo-stil-komfort-i-nadezhnost/
Блог о здоровье, красоте, полезные советы на каждый день в быту и на даче https://lmoroshkina.ru/
Новости, обзоры, тест-драйвы, ремонт и эксплуатация автомобилей https://5go.ru/
Журнал о психологии и отношениях, чувствах и эмоциях, здоровье и отдыхе. О том, что с нами происходит в жизни. Для тех, кто хочет понять себя и других https://inormal.ru/
Дача и огород, фермерство и земледелие, растения и цветы. Все о доме, даче и загородной жизне. Мы публикуем различные мнения, статьи и видеоматериалы о даче, огороде https://sad-i-dom.com/
Ежедневные актуальные новости про самые важные события в мире и России. Также публикация аналитических статей на тему общества, экономики, туризма и автопрома https://telemax-net.ru/
Ежедневные актуальные новости про самые важные события в мире и России. Также публикация аналитических статей на тему общества, экономики, туризма и автопрома https://telemax-net.ru/
Дача и огород, фермерство и земледелие, растения и цветы. Все о доме, даче и загородной жизне. Мы публикуем различные мнения, статьи и видеоматериалы о даче, огороде https://sad-i-dom.com/
Журнал о психологии и отношениях, чувствах и эмоциях, здоровье и отдыхе. О том, что с нами происходит в жизни. Для тех, кто хочет понять себя и других https://inormal.ru/
Проститутки Тюмень
Проститутки Тюмень
Проститутки Тюмени
Проститутки Тюмени
Проститутки Тюмени
Проститутки Тюмени
Проститутки Тюмень
Проститутки Тюмень
Проститутки Тюмени
Проститутки Тюмень
Индивидуалки Тюмени
Проститутки Тюмени
Проститутки Тюмени
Проститутки Тюмень
הקודמת, וסרגיי הרגיש את הדביקות של הזרע והמיצים של אינה בכף ידו. הוא הוביל את הזין אל הנרתיק לבד ואגיד לקנטים שלך שאני רוצה למצוץ לך, ואת נכנסת לתחתונים כשראית את הציצי, בסדר? וזה שלך check out this site
https://herronclothier.com/
Если вы проживаете в столице более 90 дней, регистрация обязательна по закону, даже при наличии постоянной прописки в другом регионе: временная регистрация в москве
למים. – בסדר-היא אמרה בגורל והחלה לפתוח את החזייה, כשידיה מאחורי גבה. אביו החל לעזור לו. וכך נערות ליווי. אם הם מקבלים את זה, אז שיהיה. דים, אלה רק טיפולי אמבטיה. אתה כבר מבוגר. אז דירה דיסקרטית בראשון לציון
Сделать временную регистрацию в столице можно с полным пакетом документов и официальным договором найма https://registraceja-v-moskverus-1669.ru/
Шлюхи Тюмени
Временная прописка в Москве нужна всем, кто планирует находиться в городе более трёх месяцев, независимо от цели пребывания https://registraceja-v-moskverus-1669.ru/
Шлюхи Тюмени
steam desktop authenticator
https://authenticatorsteamdesktop.com/
steam account authenticator
Обязательно попробуйте https://www.valnet.ru/canalit/obzor-sovremennogo-avtomobilnogo-rinka
https://dengi-vdolg.ru/
На нашем сайте знакомств вас ждут девушки, готовые провести вечер в интересной беседе или активном отдыхе, выберите свою идеальную пару и дайте шанс своему вечеру стать насыщенным и веселым https://spb-night.com/
Открой для себя увлекательный способ провести время с удивительными девушками, каждая из которых станет искренней поддержкой и нужной поддержкой. На сайте собраны созидательные личности, готовые сделать любое приключение особенным: проститутки в питере
Бесплатная юридическая консультация онлайн и по телефону. Получите экспертное мнение по вашей ситуации от практикующих юристов. Быстро, профессионально и без обязательств: номер телефона юриста бесплатная консультация по телефону
Бесплатная юридическая консультация онлайн и по телефону. Получите экспертное мнение по вашей ситуации от практикующих юристов. Быстро, профессионально и без обязательств: номер телефона юриста бесплатная консультация по телефону
рейтинг мфо
Avoid health risks and AC breakdowns with scheduled duct cleaning in Dubai: ac cleaning price
Dubai residents trust us for fast and effective AC duct cleaning at competitive rates: ac system cleaning cost
Возникли проблемы с законом? Не откладывайте решение. Квалифицированные юристы окажут бесплатную первичную консультацию. Проанализируем вашу ситуацию и предложим варианты действий: бесплатная юридическая консультация по телефону
Возникли проблемы с законом? Не откладывайте решение. Квалифицированные юристы окажут бесплатную первичную консультацию. Проанализируем вашу ситуацию и предложим варианты действий: консультации юриста бесплатно по телефону
Крайне рекомендую https://store.tryfusionmarketing.com/hello-world/
Советую https://marriagedesigner.com/faq/becoming-one-pic/
Очень советую https://www.radiohofeilat.co.il/%d7%90%d7%a1%d7%99-%d7%90%d7%99%d7%99%d7%9c%d7%95%d7%9f-abigail/
Крайне советую https://www.wienumzug.com/hallo-welt/
Крайне советую https://hargakamar.com/harga-kamar-hotel-claro-kendari/
Крайне советую https://www.bolnewspress.com/economia/presidente-arce-inaugura-obras-de-enlosetado-en-22-municipios-de-oruro/
Нужен монтаж отопления в Алматы? Профессиональные специалисты быстро и качественно установят систему отопления в доме, квартире или офисе. Работаем с любыми типами оборудования, даём гарантию и обеспечиваем выезд в течение часа. Доступные цены и индивидуальный подход к каждому клиенту: установка систем отопления
Нужен монтаж отопления в Алматы? Профессиональные специалисты быстро и качественно установят систему отопления в доме, квартире или офисе. Работаем с любыми типами оборудования, даём гарантию и обеспечиваем выезд в течение часа. Доступные цены и индивидуальный подход к каждому клиенту: стоимость монтажа системы отопления дома
Очень советую https://northernsteelsoccer.com/2023/09/23/winter-rivets-training-program-is-back/
Крайне рекомендую https://itruckinc.com/how-does-factoring-work
Советую https://www.alderstroest-bbr.dk/2020/09/25/beboermoede-2020-referat/
Советую https://currentdrive.pl/logo-2/
Очень советую https://seobacklinksbuilders.com/profile-backlink-list-2025/comment-page-19/
Очень советую https://srhealthinsurancepros.com/hello-world/
Крайне советую http://70faces.org/?page_id=2&cpage=11679
Обязательно попробуйте http://germaneuro.com/bbs/board.php?bo_table=qa&wr_id=30
Советую http://dftelhas.com.br/ola-mundo/?unapproved=52898&moderation-hash=5c3ffb949b81260700781b61b780ad1d
Предлагаю https://digitalcanvasartistry.com/hello-world/
Крайне рекомендую https://sly-fox.at/wordpress/?p=62
Крайне советую https://www.texicureans.com/roasted-corn-salad/
Попробуйте https://plaka-watersports.com/safety/
Крайне советую https://flower-tone.com/news/new/855/
Попробуйте https://schasociados.com/hello-world/
Excellent post. Keep posting such kind of info on your page. Im really impressed by your blog.
Hey there, You’ve performed an incredible job. I will definitely digg it and personally suggest to my friends. I’m confident they’ll be benefited from this web site.
tadalafil 5 mg effetto
Обязательно попробуйте http://www.inazawa-lab.com/machidukuri/forums/topic/dfecwecrw/page/557/#post-413365
Предлагаю https://agwoserwis.pl/index.php/2021/04/23/hello-world/
Крайне советую http://empfehlung.decocco.de/
Очень советую https://southseasimpex.com/south-seas-logo/
Советую https://fin-gu.ru/imag1620_2/
Обязательно попробуйте http://www.mergellandmannenkoor.nl/nieuw/dr-put/dr-put/
Попробуйте http://fandrunionhills.com/hello-world/
Лучшие онлайн-курсы https://topkursi.ru по востребованным направлениям: от маркетинга до программирования. Учитесь в удобное время, получайте сертификаты и прокачивайте навыки с нуля.
Школа Саморазвития https://bznaniy.ru онлайн-база знаний для тех, кто хочет понять себя, улучшить мышление, прокачать навыки и выйти на новый уровень жизни.
Se você está procurando a melhor experiência de cassino online com slots emocionantes, não pode deixar de baixar Big Bass Bonanza agora mesmo! Este caça-níquel da Pragmatic Play, conhecido como Big Bass Bonanza, conquistou os jogadores com seu visual divertido e prêmios incríveis https://big-bassbonanza.com/
Se você está procurando a melhor experiência de cassino online com slots emocionantes, não pode deixar de baixar Big Bass Bonanza agora mesmo! Este caça-níquel da Pragmatic Play, conhecido como Big Bass Bonanza, conquistou os jogadores com seu visual divertido e prêmios incríveis https://big-bassbonanza.com/
Попробуйте https://bachmassage.com/bach-logo/
Предлагаю https://www.inkbyschnitzel.de/ibsicon/
Thanks for every other fantastic post. Where else may just anybody get that kind of info in such a perfect approach of writing? I’ve a presentation subsequent week, and I am on the look for such info.
cialis 2 5 mg prezzo in farmacia
Лучшие онлайн-курсы https://topkursi.ru по востребованным направлениям: от маркетинга до программирования. Учитесь в удобное время, получайте сертификаты и прокачивайте навыки с нуля.
Lightweight object for all people on this site https://easygrawvf52.com/
Easy tasks for all people on this site https://easygrawvf52.com/
Магнитные бури http://inforigin.ru/ .
Календарь огородника http://www.istoriamashin.ru .
Какой сегодня церковный праздник http://www.topoland.ru/ .
3 сезон игры в кальмара смотреть бесплатно – южнокорейский сериал о смертельных играх на выживание ради огромного денежного приза. Сотни отчаявшихся людей участвуют в детских играх, где проигрыш означает смерть. Сериал исследует темы социального неравенства, морального выбора и человеческой природы в экстремальных условиях.
Лунные день сегодня https://www.pechory-online.ru .
Лунный календарь http://novorjev.ru/ .
Южнокорейский сериал о смертельных играх на выживание ради огромного денежного приза: 3 сезон игра в кальмара смотреть онлайн. Сотни отчаявшихся людей участвуют в детских играх, где проигрыш означает смерть. Сериал исследует темы социального неравенства, морального выбора и человеческой природы в экстремальных условиях.