Git Product home page Git Product logo

meilisync's Introduction

meilisync

image image image image PyPI - Python Version

Introduction

Realtime sync data from MySQL/PostgreSQL/MongoDB to Meilisearch.

There is also a web admin dashboard for meilisync meilisync-admin.

Install

Install from pypi:

  • pip install meilisync[mysql] for MySQL.
  • pip install meilisync[postgres] for PostgreSQL.
  • pip install meilisync[mongo] for MongoDB.
  • pip install meilisync[all] for all.
  • pip install meilisync[redis] for redis progress.

Use docker (Recommended)

You can use docker to run meilisync:

version: "3"
services:
  meilisync:
    image: long2ice/meilisync
    volumes:
      - ./config.yml:/meilisync/config.yml
    restart: always

Prerequisites

  • MySQL: binlog_format = ROW, use binary log.
  • PostgreSQL: wal_level = logical and install wal2json extension, use logical replication.
  • MongoDB: enable replica set mode, use change stream.

Quick Start

If you run meilisync without any arguments, it will try to load the configuration from config.yml in the current directory.

❯ meilisync --help

 Usage: meilisync [OPTIONS] COMMAND [ARGS]...

╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --config              -c      TEXT  Config file path [default: config.yml]                                                                                                         │
│ --install-completion                Install completion for the current shell.                                                                                                      │
│ --show-completion                   Show completion for the current shell, to copy it or customize the installation.                                                               │
│ --help                              Show this message and exit.                                                                                                                    │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ check            Check whether the data in the database is consistent with the data in Meilisearch                                                                                 │
│ refresh          Refresh all data by swap index                                                                                                                                    │
│ start            Start meilisync                                                                                                                                                   │
│ version          Show meilisync version                                                                                                                                            │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Start sync

Start sync data from MySQL to Meilisearch:

❯ meilisync start
2023-03-07 08:37:25.656 | INFO     | meilisync.main:_:86 - Start increment sync data from "mysql" to Meilisearch...

Refresh sync

Refresh all data by swap index:

❯ meilisync refresh -t test

Before refresh, you need stop the sync process first to avoid data inconsistency.

Check sync

Check whether the data count in the database is consistent with the data in Meilisearch:

❯ meilisync check -t test

Configuration

Here is an example configuration file:

debug: true
plugins:
  - meilisync.plugin.Plugin
progress:
  type: file
source:
  type: mysql
  host: 192.168.123.205
  port: 3306
  user: root
  password: "123456"
  database: beauty
meilisearch:
  api_url: http://192.168.123.205:7700
  api_key:
  insert_size: 1000
  insert_interval: 10
sync:
  - table: collection
    index: beauty-collections
    plugins:
      - meilisync.plugin.Plugin
    full: true
    fields:
      id:
      title:
      description:
      category:
  - table: picture
    index: beauty-pictures
    full: true
    fields:
      id:
      description:
      category:
sentry:
  dsn: ""
  environment: "production"

debug (optional)

Enable debug mode, default is false, if you want to see more logs, you can set it to true.

plugins (optional)

The plugins are used to customize the data before or after insert to Meilisearch and the plugins is a list of python modules.

Which is a python class with pre_event and post_event methods, the pre_event method is called before insert to Meilisearch, the post_event method is called after insert to Meilisearch.

class Plugin:
    is_global = False

    async def pre_event(self, event: Event):
        logger.debug(f"pre_event: {event}, is_global: {self.is_global}")
        return event

    async def post_event(self, event: Event):
        logger.debug(f"post_event: {event}, is_global: {self.is_global}")
        return event

The is_global is used to indicate whether the plugin instance is global, if set to True, the plugin instance will be created only once, otherwise, the plugin instance will be created for each event.

progress

The progress is used to record the last sync position, such as binlog position for MySQL.

  • type: file or redis, if set to file, another option path is required.
  • path: the file path to store the progress, default is progress.json.
  • key: the redis key to store the progress, default is meilisync:progress.
  • dsn: the redis dsn, default is redis://localhost:6379/0.

source

Source database configuration, currently only support MySQL and PostgreSQL and MongoDB.

  • type: mysql or postgres or mongo.
  • server_id: the server id for MySQL binlog, default is 1.
  • database: the database name.
  • other keys: the database connection arguments, MySQL see asyncmy, PostgreSQL see psycopg2, MongoDB see motor.

meilisearch

Meilisearch configuration.

  • api_url: the Meilisearch API URL.
  • api_key: the Meilisearch API key.
  • insert_size: insert after collecting this many documents, optional.
  • insert_interval: insert after this many seconds have passed, optional.

If nether insert_size nor insert_interval is set, it will insert each document immediately.

If you prefer performance, just set and increase insert_size and insert_interval. The insert will be made as long as one of the conditions is met.

sync

The sync configuration, you can add multiple sync tasks.

  • table: the database table name or collection name.
  • index: the Meilisearch index name, if not set, it will use the table name.
  • full: whether to do a full sync, default is false.
  • fields: the fields to sync, if not set, it will sync all fields. The key is table field name, the value is the Meilisearch field name, if not set, it will use the table field name.
  • plugins: the table level plugins, optional.

sentry (optional)

Sentry configuration.

  • dsn: the sentry dsn.
  • environment: the sentry environment, default is production.

License

This project is licensed under the Apache-2.0 License.

meilisync's People

Contributors

brunoocasali avatar dependabot[bot] avatar gnosisai avatar gorostislav avatar kdmw-io avatar lasseintree avatar long2ice avatar mattexact avatar s1berc0de avatar sanders41 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

meilisync's Issues

Data not syncing

Data is not syncing between postgres:16-bookworm ( with wal2json installed) and meilisync.

Logs:

2024-01-13 09:02:33 2024-01-13 08:02:33.273 | INFO     | meilisync.main:_:101 - Start increment sync data from "SourceType.postgres" to MeiliSearch...
2024-01-13 09:02:33 2024-01-13 08:02:33.284 | DEBUG    | meilisync.main:_:104 - progress={'start_lsn': '0/19B5B50'}

Logs PSQL:

2024-01-13 09:02:33 2024-01-13 08:02:33.275 UTC [35] ERROR:  replication slot "meilisync" already exists
2024-01-13 09:02:33 2024-01-13 08:02:33.275 UTC [35] STATEMENT:  CREATE_REPLICATION_SLOT "meilisync" LOGICAL "wal2json"
2024-01-13 09:02:33 2024-01-13 08:02:33.279 UTC [35] LOG:  0/19B5B50 has been already streamed, forwarding to 0/19B5B88
2024-01-13 09:02:33 2024-01-13 08:02:33.279 UTC [35] STATEMENT:  START_REPLICATION SLOT "meilisync" LOGICAL 0/019B5B50 ("include-lsn" 'true')
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] LOG:  starting logical decoding for slot "meilisync"
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] DETAIL:  Streaming transactions committing after 0/19B5B88, reading WAL from 0/19B5B50.
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] STATEMENT:  START_REPLICATION SLOT "meilisync" LOGICAL 0/019B5B50 ("include-lsn" 'true')
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] LOG:  logical decoding found consistent point at 0/19B5B50
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] DETAIL:  There are no running transactions.
2024-01-13 09:02:33 2024-01-13 08:02:33.283 UTC [35] STATEMENT:  START_REPLICATION SLOT "meilisync" LOGICAL 0/019B5B50 ("include-lsn" 'true')

Some help is appreciated!

ERROR: ModuleNotFoundError: No module named 'motor'

I'm trying to test the synchronization between meilisearch and mysql, at first I followed the documentation with the installation without docker, and when trying to use it, it's giving me this error as if the import wasn't correct. Is this an error in my installation or could there really be a problem in this version?

image

Batch MySQL insert only synching the first occurence

Batch MySQL insert only synching the first occurence:

INSERT INTO categories (id, category_parent_id, category_name_fr, category_name_ar, category_name_en, category_slug) VALUES
(1, 0, 'Animaux', 'حيوانات', 'Pets', 'animaux'),
(2, 0, 'Art, antiquités', 'الفن والتحف', 'Art, antiques', 'art-antiquites'),
(3, 0, 'Accessoires et pièces auto, moto', 'إكسسوارات وقطع غيار السيارات والدراجات النارية\r\n', 'Auto and motorcycle accessories and parts', 'accessoires-pièces-auto-moto'),
(4, 0, 'Bateaux, voile, nautisme', 'القوارب، الشراعية، بحري', 'Boats, sailing, nautical', 'Bateaux-voile-nautisme'),
(5, 0, 'Beauté, bien-être, parfums', 'الجمال والرفاهية والعطور', 'Beauty, well-being, perfumes', 'beaute-bien-être-parfums'),
(6, 0, 'Bijoux, montres', 'المجوهرات والساعات', 'Jewelry, watches', 'bijoux-montres'),
(7, 0, 'Bricolage', 'أعمال يدوية', 'DIY', 'bricolage'),
(8, 0, 'Bébé, puériculture', 'الطفل، رعاية الأطفال', 'Child, childcare', 'Bebe-puericulture'),
(9, 0, 'Collections', 'المجموعات', 'Collections', 'collections'),
(10, 0, 'Céramiques, verrerie', 'السيراميك والأواني الزجاجية', 'Ceramics, glassware', 'ceramiques-verrerie'),
(11, 0, 'DVD, cinéma', 'دي في دي، سينما', 'DVD, cinema', 'dvd-cinema'),
(12, 0, 'Gastronomie, boissons', 'فن الطهو والمشروبات', 'Gastronomy, drinks', 'gastronomie-boissons'),
(13, 0, 'Image, son', 'الصورة، الصوت', 'Image, sound', 'image-son'),
(14, 0, 'Immobilier', 'العقارات', 'Real estate', 'immobilier'),
(15, 0, 'Informatique, réseaux', 'تكنولوجيا المعلومات والشبكات\r\n', 'Computer, networks', 'informatique-reseaux'),
(16, 0, 'Instruments de musique', 'آلات موسيقية', 'Musical instruments', 'instruments-musique'),
(17, 0, 'Jardin, terrasse', 'حديقة، تراس', 'Garden, terrace', 'jardin-terrasse'),
(18, 0, 'Jeux vidéo, consoles de jeux', 'ألعاب الفيديو، وحدات تحكم الألعاب\r\n', 'Video games, game consoles', 'Jeux-video-consoles-jeux'),
(19, 0, 'Jouets et jeux', 'اللعب والألعاب', 'Toys and games', 'jouets-jeux'),
(20, 0, 'Livres, BD, revues', 'الكتب والقصص المصورة والمجلات\r\n', 'Books, comics, magazines', 'livres-BD-revues'),
(21, 0, 'Loisirs créatifs', 'الهوايات الإبداعية', 'Creative hobbies\r\n', 'loisirs-creatifs'),
(22, 0, 'Maison', 'المنزل', 'Home', 'maison'),
(23, 0, 'Monnaies', 'عملات معدنية', 'Coins', 'monnaies'),
(24, 0, 'Musique, CD, vinyles', 'الموسيقى والأقراص المدمجة والفينيل\r\n', 'Music, CDs, vinyls\r\n', 'musique-CD-vinyles'),
(25, 0, 'Photo, caméscopes', 'الصور وكاميرات الفيديو', 'Photo, camcorders', 'photo-camescopes'),
(26, 0, 'Sports, vacances', 'الرياضة والإجازات', 'Sports, vacations', 'sports-vacances'),
(27, 0, 'Timbres', 'طوابع بريدية', 'Stamps', 'timbres'),
(28, 0, 'Téléphonie, mobilité', 'الهاتف والتنقل\r\n', 'Telephony, mobility\r\n', 'telephonie-mobilite'),
(29, 0, 'Vêtements, accessoires', 'الملابس والإكسسوارات\r\n', 'Clothing & Accessories', 'vetements-accessoires'),
(30, 0, 'Électroménager', 'الأجهزة المنزلية', 'Home appliance\r\n', 'electromenager'),
(31, 0, 'Équipements professionnels', 'المعدات المهنية\r\n', 'Professional equipment\r\n', 'equipements-professionnels');

Only the first value : (1, 0, 'Animaux', 'حيوانات', 'Pets', 'animaux') is added (synched)

Please note that on windows 11 without docker.

TypeError: 'NoneType' object is not subscriptable (ret = None)

2024-03-27 06:21:05.534 | INFO | meilisync.main::101 - Start increment sync data from "SourceType.mysql" to MeiliSearch...
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /meilisync/meilisync/main.py:140 in start │
│ │
│ 137 │ │ lock = asyncio.Lock() │
│ 138 │ │ await asyncio.gather(
(), interval()) │
│ 139 │ │
│ ❱ 140 │ asyncio.run(run()) │
│ 141 │
│ 142 │
│ 143 @app.command(help="Refresh all data by swap index") │
│ │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ _ = <function start.._ at 0x7fe7ee59c2c0> │ │
│ │ collection = <meilisync.event.EventCollection object at │ │
│ │ 0x7fe7ef2150d0> │ │
│ │ context = <click.core.Context object at 0x7fe7ef566000> │ │
│ │ current_progress = None │ │
│ │ interval = <function start..interval at 0x7fe7ee59c180> │ │
│ │ lock = <asyncio.locks.Lock object at 0x7fe7ee97b170 │ │
│ │ [unlocked]> │ │
│ │ meili = <meilisync.meili.Meili object at 0x7fe7ef802b10> │ │
│ │ meili_settings = MeiliSearch( │ │
│ │ │ api_url='http://host.docker.internal:7700', │ │
│ │ │ │ │
│ │ api_key='2e4b99ff802ebfcb96b9134ad74cbcdd5297af4547a… │ │
│ │ │ insert_size=1000, │ │
│ │ │ insert_interval=10 │ │
│ │ ) │ │
│ │ progress = <meilisync.progress.file.File object at │ │
│ │ 0x7fe7ef1c9d60> │ │
│ │ run = <function start..run at 0x7fe7ee59d620> │ │
│ │ settings = Settings( │ │
│ │ │ plugins=[], │ │
│ │ │ progress=Progress( │ │
│ │ │ │ type=<ProgressType.file: 'file'> │ │
│ │ │ ), │ │
│ │ │ debug=False, │ │
│ │ │ source=Source( │ │
│ │ │ │ type=<SourceType.mysql: 'mysql'>, │ │
│ │ │ │ database='aars', │ │
│ │ │ │ host='host.docker.internal', │ │
│ │ │ │ port=3306, │ │
│ │ │ │ user='usr', │ │
│ │ │ │ password='12345' │ │
│ │ │ ), │ │
│ │ │ meilisearch=MeiliSearch( │ │
│ │ │ │ api_url='http://host.docker.internal:7700', │ │
│ │ │ │ │ │
│ │ api_key='2e4b99ff802ebfcb96b9134ad74cbcdd5297af4547a… │ │
│ │ │ │ insert_size=1000, │ │
│ │ │ │ insert_interval=10 │ │
│ │ │ ), │ │
│ │ │ sync=[ │ │
│ │ │ │ Sync( │ │
│ │ │ │ │ plugins=[], │ │
│ │ │ │ │ table='bb', │ │
│ │ │ │ │ pk='id', │ │
│ │ │ │ │ full=False, │ │
│ │ │ │ │ index='products', │ │
│ │ │ │ │ fields=None │ │
│ │ │ │ ) │ │
│ │ │ ], │ │
│ │ │ sentry=None │ │
│ │ ) │ │
│ │ source = <meilisync.source.mysql.MySQL object at │ │
│ │ 0x7fe7ef777e30> │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /usr/local/lib/python3.12/asyncio/runners.py:194 in run │
│ │
│ 191 │ │ │ "asyncio.run() cannot be called from a running event loop" │
│ 192 │ │
│ 193 │ with Runner(debug=debug, loop_factory=loop_factory) as runner: │
│ ❱ 194 │ │ return runner.run(main) │
│ 195 │
│ 196 │
│ 197 def _cancel_all_tasks(loop): │
│ │
│ ╭──────────────────────────────── locals ────────────────────────────────╮ │
│ │ debug = None │ │
│ │ loop_factory = None │ │
│ │ main = <coroutine object start..run at 0x7fe7ee5acba0> │ │
│ │ runner = <asyncio.runners.Runner object at 0x7fe7ef2146b0> │ │
│ ╰────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /usr/local/lib/python3.12/asyncio/runners.py:118 in run │
│ │
│ 115 │ │ │
│ 116 │ │ self._interrupt_count = 0 │
│ 117 │ │ try: │
│ ❱ 118 │ │ │ return self._loop.run_until_complete(task) │
│ 119 │ │ except exceptions.CancelledError: │
│ 120 │ │ │ if self._interrupt_count > 0: │
│ 121 │ │ │ │ uncancel = getattr(task, "uncancel", None) │
│ │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ context = <_contextvars.Context object at 0x7fe7f17c76c0> │ │
│ │ coro = <coroutine object start..run at 0x7fe7ee5acba0> │ │
│ │ self = <asyncio.runners.Runner object at 0x7fe7ef2146b0> │ │
│ │ sigint_handler = functools.partial(<bound method Runner.on_sigint of │ │
│ │ <asyncio.runners.Runner object at 0x7fe7ef2146b0>>, │ │
│ │ main_task=<Task finished name='Task-4' │ │
│ │ coro=<start..run() done, defined at │ │
│ │ /meilisync/meilisync/main.py:135> │ │
│ │ exception=TypeError("'NoneType' object is not │ │
│ │ subscriptable")>) │ │
│ │ task = <Task finished name='Task-4' coro=<start..run() │ │
│ │ done, defined at /meilisync/meilisync/main.py:135> │ │
│ │ exception=TypeError("'NoneType' object is not │ │
│ │ subscriptable")> │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /usr/local/lib/python3.12/asyncio/base_events.py:664 in run_until_complete │
│ │
│ 661 │ │ if not future.done(): │
│ 662 │ │ │ raise RuntimeError('Event loop stopped before Future comp │
│ 663 │ │ │
│ ❱ 664 │ │ return future.result() │
│ 665 │ │
│ 666 │ def stop(self): │
│ 667 │ │ """Stop running the event loop. │
│ │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ future = <Task finished name='Task-4' coro=<start..run() done, │ │
│ │ defined at /meilisync/meilisync/main.py:135> │ │
│ │ ### exception=TypeError("'NoneType' object is not │ │
│ │ subscriptable")>
│ │
│ │ new_task = False │ │
│ │ self = <UnixSelectorEventLoop running=False closed=True │ │
│ │ debug=False> │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /meilisync/meilisync/main.py:138 in run │
│ │
│ 135 │ async def run(): │
│ 136 │ │ nonlocal lock │
│ 137 │ │ lock = asyncio.Lock() │
│ ❱ 138 │ │ await asyncio.gather(
(), interval()) │
│ 139 │ │
│ 140 │ asyncio.run(run()) │
│ 141 │
│ │
│ ╭────────────────────────────── locals ───────────────────────────────╮ │
│ │ _ = <function start..
at 0x7fe7ee59c2c0> │ │
│ │ interval = <function start..interval at 0x7fe7ee59c180> │ │
│ │ lock = <asyncio.locks.Lock object at 0x7fe7ee97b170 [unlocked]> │ │
│ ╰─────────────────────────────────────────────────────────────────────╯ │
│ │
│ /meilisync/meilisync/main.py:102 in _ │
│ │
│ 99 │ │ │ │ │ │ f'No data found for table "{settings.source.da │
│ 100 │ │ │ │ │ ) │
│ 101 │ │ logger.info(f'Start increment sync data from "{settings.source │
│ ❱ 102 │ │ async for event in source: │
│ 103 │ │ │ if settings.debug: │
│ 104 │ │ │ │ logger.debug(event) │
│ 105 │ │ │ current_progress = event.progress │
│ │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
│ │ collection = <meilisync.event.EventCollection object at │ │
│ │ 0x7fe7ef2150d0> │ │
│ │ current_progress = None │ │
│ │ lock = <asyncio.locks.Lock object at 0x7fe7ee97b170 │ │
│ │ [unlocked]> │ │
│ │ meili = <meilisync.meili.Meili object at 0x7fe7ef802b10> │ │
│ │ meili_settings = MeiliSearch( │ │
│ │ │ api_url='http://host.docker.internal:7700', │ │
│ │ │ │ │
│ │ api_key='2e4b99ff802ebfcb96b9134ad74cbcdd5297af4547a… │ │
│ │ │ insert_size=1000, │ │
│ │ │ insert_interval=10 │ │
│ │ ) │ │
│ │ progress = <meilisync.progress.file.File object at │ │
│ │ 0x7fe7ef1c9d60> │ │
│ │ settings = Settings( │ │
│ │ │ plugins=[], │ │
│ │ │ progress=Progress( │ │
│ │ │ │ type=<ProgressType.file: 'file'> │ │
│ │ │ ), │ │
│ │ │ debug=False, │ │
│ │ │ source=Source( │ │
│ │ │ │ type=<SourceType.mysql: 'mysql'>, │ │
│ │ │ │ database='aars', │ │
│ │ │ │ host='host.docker.internal', │ │
│ │ │ │ port=3306, │ │
│ │ │ │ user='usr', │ │
│ │ │ │ password='12345' │ │
│ │ │ ), │ │
│ │ │ meilisearch=MeiliSearch( │ │
│ │ │ │ api_url='http://host.docker.internal:7700', │ │
│ │ │ │ │ │
│ │ api_key='2e4b99ff802ebfcb96b9134ad74cbcdd5297af4547a… │ │
│ │ │ │ insert_size=1000, │ │
│ │ │ │ insert_interval=10 │ │
│ │ │ ), │ │
│ │ │ sync=[ │ │
│ │ │ │ Sync( │ │
│ │ │ │ │ plugins=[], │ │
│ │ │ │ │ table='bb', │ │
│ │ │ │ │ pk='id', │ │
│ │ │ │ │ full=False, │ │
│ │ │ │ │ index='products', │ │
│ │ │ │ │ fields=None │ │
│ │ │ │ ) │ │
│ │ │ ], │ │
│ │ │ sentry=None │ │
│ │ ) │ │
│ │ source = <meilisync.source.mysql.MySQL object at │ │
│ │ 0x7fe7ef777e30> │ │
│ │ sync = Sync( │ │
│ │ │ plugins=[], │ │
│ │ │ table='bb', │ │
│ │ │ pk='id', │ │
│ │ │ full=False, │ │
│ │ │ index='products', │ │
│ │ │ fields=None │ │
│ │ ) │ │
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
│ │
│ /meilisync/meilisync/source/mysql.py:93 in aiter
│ │
│ 90 │ │ self.conn = await asyncmy.connect(**self.kwargs) │
│ 91 │ │ self.ctl_conn = await asyncmy.connect(**self.kwargs) │
│ 92 │ │ if not self.progress: │
│ ❱ 93 │ │ │ self.progress = await self.get_current_progress() │
│ 94 │ │ yield ProgressEvent( │
│ 95 │ │ │ progress=self.progress, │
│ 96 │ │ ) │
│ │
│ ╭──────────────────────────── locals ────────────────────────────╮ │
│ │ self = <meilisync.source.mysql.MySQL object at 0x7fe7ef777e30> │ │
│ ╰────────────────────────────────────────────────────────────────╯ │
│ │
│ /meilisync/meilisync/source/mysql.py:70 in get_current_progress │
│ │
│ 67 │ │ │ │ await cur.execute("SHOW MASTER STATUS") │
│ 68 │ │ │ │ ret = await cur.fetchone() │
│ 69 │ │ │ │ return { │
│ ❱ 70 │ │ │ │ │ "master_log_file": ret["File"], │
│ 71 │ │ │ │ │ "master_log_position": ret["Position"], │
│ 72 │ │ │ │ } │
│ 73 │
│ │
│ ╭──────────────────────────── locals ─────────────────────────────╮ │
│ │ conn = <asyncmy.connection.Connection object at 0x7fe7ee965e50> │ │
│ │ cur = <asyncmy.cursors.DictCursor object at 0x7fe7ee13e8d0> │ │
│ │ ret = None │ │
│ │ self = <meilisync.source.mysql.MySQL object at 0x7fe7ef777e30> │ │
│ ╰─────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────╯
TypeError: 'NoneType' object is not subscriptable
Exception ignored in: <function StreamWriter.del at 0x7fe7f19faf20>
Traceback (most recent call last):
File "/usr/local/lib/python3.12/asyncio/streams.py", line 397, in del
File "/usr/local/lib/python3.12/asyncio/streams.py", line 343, in close
File "/usr/local/lib/python3.12/asyncio/selector_events.py", line 1206, in close
File "/usr/local/lib/python3.12/asyncio/selector_events.py", line 871, in close
File "/usr/local/lib/python3.12/asyncio/base_events.py", line 772, in call_soon
File "/usr/local/lib/python3.12/asyncio/base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed
Exception ignored in: <function StreamWriter.del at 0x7fe7f19faf20>
Traceback (most recent call last):
File "/usr/local/lib/python3.12/asyncio/streams.py", line 397, in del
File "/usr/local/lib/python3.12/asyncio/streams.py", line 343, in close
File "/usr/local/lib/python3.12/asyncio/selector_events.py", line 1206, in close
File "/usr/local/lib/python3.12/asyncio/selector_events.py", line 871, in close
File "/usr/local/lib/python3.12/asyncio/base_events.py", line 772, in call_soon
File "/usr/local/lib/python3.12/asyncio/base_events.py", line 519, in _check_closed
RuntimeError: Event loop is closed

image

Synchronisation error when importing from MongoDB

Description

I tried to import documents with meilisync from the MongoDB and got an error:

TypeError: meilisync.progress.file.File.set() argument after ** must be a mapping, not NoneType
2023-08-02 09:52:16.401 | ERROR    | meilisync.main:interval:122 - Error when insert data to MeiliSearch: meilisync.progress.file.File.set() argument after ** must be a mapping, not NoneType

Data format:

I used this sample data:

{
  "id": "287947",
  "title": "Shazam!",
  "poster": "https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg",
  "overview": "A boy is given the ability to become an adult superhero in times of need with a single magic word.",
  "release_date": { "$numberLong": "1553299200" }
}

Configuration file

debug: true
plugins:
  - meilisync.plugin.Plugin
progress:
  type: file
source:
  type: mongo
  host: mongodb+srv://cluster_IP.mongodb.net
  port: 27017
  username: amelie
  password: ***
  database: database_name
meilisearch:
  api_url: http://localhost:7700/
  api_key: 'masterKey'
  insert_size: 1000
  insert_interval: 10
sync:
  - table: movies
    index: movies
    fields:
      id:
      title:

Screenshots & Logs:

I put my logs in a file in case:
output_error.txt

TypeError: 'async for' requires an object with __aiter__ method, got coroutine

For syncing databases with PostgreSQL and Meilisync,

I installed and configured Meilisync (windows 10) by following the steps in https://www.meilisearch.com/docs/learn/cookbooks/meilisync_postgresql
Meilisync was well-configured:
1- image (1)
2- but for the Meilisync start, i received
TypeError: 'async for' requires an object with aiter method, got generator
returned from
C:\Python312\Lib\site-packages\meilisync\main.py:96 in _ │
│ │
│ 93 │ │ for sync in settings.sync: │
│ 94 │ │ │ if sync.full and not await meili.index_exists(sync.index_name): │
│ 95 │ │ │ │ count = 0 │
│ > 96 │ │ │ │ async for items in await source.get_full_data(sync, meili_settings.inser │
│ 97 │ │ │ │ │ count += len(items) │
│ 98 │ │ │ │ │ await meili.add_data(sync, items) │
│ 99 │ │ │ │ if count:

image

image (2)
the config.yml:

meilisearch:
  api_url: http://localhost:7700/
  api_key: '_API KEY_'
  insert_size: 1000
  insert_interval: 10

source:
  type: postgres
  host: 127.0.0.1
  port: 5432
  database: _DATABASE NAME_
  user: postgres
  password: root

progress:
  type: file  
  path: 'C:\\Python312\\Lib\\site-packages\\meilisync\\progress.json'

sync:
 
  - table: bookings
    index: bookings
    full: true
   
debug: true

kindly, Any idea on how to solve this issue?

Not Fetching Data when specifying field attribute in config.yml

Describe the bug
I successfully Connected meilisync with my MongoDB Atlas Database. If I don't use the "fields" argument in the yaml file, I receive the data, however as soon as I specify the fields, all data is None.

My config.yml:

plugins:
  - meilisync.plugin.Plugin
progress:
  type: file
  path: process.json
source:
  type: mongo
  host: ...
  username:...
  password:...
  database:...
meilisearch:
  api_url: ...
  api_key: ...
  insert_size: 1000
  insert_interval: 10
sync:
  - table: userschemas
    index: userschemas
    full: true
    pk: _id
    fields:
      _id:
      email:
      userName:
      firstName:
      lastName:
      gender:

Example of some dummy Data in Mongo DB:
image

Logging without Specification:
image

Logging with Specification:
image

Expected behavior:
Should Catch the Data given the provided fields

Desktop (please complete the following information):

  • OS: Mac m1
  • Python version: 3.11
  • functime version: 0.1.3
  • MongoDB Atlas
  • meilisearch Cloud

TypeError: Object of type Decimal is not JSON serializable


2024-04-23T11:14:05.440428411Z │ │           o = [                                                          │ │
2024-04-23T11:14:05.440433580Z │ │               │   {                                                      │ │
2024-04-23T11:14:05.440438329Z │ │               │   │   'filename':                                        │ │
2024-04-23T11:14:05.440444615Z │ │               'ca57298e615ab2d4875db18590befaad.png',                    │ │
2024-04-23T11:14:05.440498394Z │ │               │   │   'data':                                           │ │
2024-04-23T11:14:05.440505169Z │ │               Decimal('1056218744737726101909875705338'),                │ │
2024-04-23T11:14:05.440536668Z │ │               │   }                                                      │ │
2024-04-23T11:14:05.440542954Z │ │               ]                                                          │ │
2024-04-23T11:14:05.440548541Z │ │        self = <json.encoder.JSONEncoder object at 0x7fc5aebe2690>        │ │
2024-04-23T11:14:05.440553779Z │ ╰──────────────────────────────────────────────────────────────────────────╯ │
2024-04-23T11:14:05.440559017Z │                                                                              │
2024-04-23T11:14:05.440562789Z │ /usr/local/lib/python3.12/json/encoder.py:180 in default                     │
2024-04-23T11:14:05.440568097Z │                                                                              │
2024-04-23T11:14:05.440572497Z │   177 │   │   │   │   return JSONEncoder.default(self, o)                    │
2024-04-23T11:14:05.440581926Z │   178 │   │                                                                  │
2024-04-23T11:14:05.440587094Z │   179 │   │   """                                                            │
2024-04-23T11:14:05.440592402Z │ ❱ 180 │   │   raise TypeError(f'Object of type {o.__class__.__name__} '      │
2024-04-23T11:14:05.440596663Z │   181 │   │   │   │   │   │   f'is not JSON serializable')                   │
2024-04-23T11:14:05.440602530Z │   182 │                                                                      │
2024-04-23T11:14:05.440608257Z │   183 │   def encode(self, o):                                               │
2024-04-23T11:14:05.440612936Z │                                                                              │
2024-04-23T11:14:05.440618104Z │ ╭────────────────────────── locals ──────────────────────────╮               │
2024-04-23T11:14:05.440623413Z │ │    o = Decimal('1056218744737726101909875705338')          │               │
2024-04-23T11:14:05.440628651Z │ │ self = <json.encoder.JSONEncoder object at 0x7fc5aebe2690> │               │
2024-04-23T11:14:05.440633051Z │ ╰────────────────────────────────────────────────────────────╯               │
2024-04-23T11:14:05.440639756Z ╰──────────────────────────────────────────────────────────────────────────────╯
2024-04-23T11:14:05.440644924Z TypeError: Object of type Decimal is not JSON serializable
2024-04-23T11:14:05.499338434Z Exception ignored in: <function StreamWriter.__del__ at 0x7fc5adf66480>
2024-04-23T11:14:05.499379851Z 

the database is not syncing

nothing happens after startup (including no synchronization with the database), there are no obvious errors either. What could be the problem?

user23@host_nl:~/meilisync$ docker-compose up
Creating meilisync_meilisync_1 ... done
Attaching to meilisync_meilisync_1
meilisync_1  | 2023-11-19 16:22:45.955 | DEBUG    | meilisync.main:_:33 - plugins=None progress=Progress(type=<ProgressType.file: 'file'>) debug=True source=Source(type=<SourceType.postgres: 'postgres'>, database='pic_monitor', user='user23', host='my_host_ip', port=5432, password='my_db_password') meilisearch=MeiliSearch(api_url='http://my_host_ip:7700', api_key='meilisearch_api_key', insert_size=1000, insert_interval=10) sync=[Sync(plugins=None, table='photos', pk='id', full=True, index='photos', fields=None)] sentry=None
meilisync_1  | 2023-11-19 16:22:50.662 | INFO     | meilisync.main:_:82 - Full data sync for table "pic_monitor.photos" done! 49281 documents added.
meilisync_1  | 2023-11-19 16:22:50.662 | INFO     | meilisync.main:_:90 - Start increment sync data from "postgres" to MeiliSearch...
meilisync_1  | 2023-11-19 16:22:50.667 | DEBUG    | meilisync.main:_:93 - progress={'start_lsn': '0/F10DEB0'}

my config.yml:

debug: true
progress:
  type: file
source:
  type: postgres
  host: my_host_ip
  port: 5432
  user: user23
  password: "my_db_password"
  database: pic_monitor
meilisearch:
  api_url: http://my_host_ip:7700
  api_key: 'meilisearch_api_key'
  insert_size: 1000
  insert_interval: 10
sync:
  - table: photos # my table name in database pic_monitor
    index: photos # my index in meilisearch
    full: true

Strange behavior during the sync

I created a MySQL database with some fake data, and I ran the sync without dates because of the #8.
After that, I inserted a new row in the table from the MySQL CLI.

The received error mentions the support of utf8mb3 encoding.

meilisync-meilisync-1    | 2023-04-21 15:10:27.964 | DEBUG    | meilisync.main:_:33 - plugins=['meilisync.plugin.Plugin'] progress=Progress(type=<ProgressType.file: 'file'>, path='./progress.json') debug=True source=Source(type=<SourceType.mysql: 'mysql'>, database='blog', port=3306, host='mysql', charset='utf8', password='meilisync', user='root') meilisearch=MeiliSearch(api_url='http://meilisearch:7700', api_key='7TL351wVlGaszDQjfXGe7J6_15ZIARF0wa_zpwXaAOU', insert_size=1000, insert_interval=10) sync=[Sync(plugins=None, table='authors', pk='id', full=True, index=None, fields={'id': None, 'first_name': None, 'last_name': None, 'email': None})] sentry=None
meilisync-meilisync-1    | 2023-04-21 15:10:28.070 | INFO     | meilisync.main:_:90 - Start increment sync data from "mysql" to MeiliSearch...
meilisync-meilisync-1    | 2023-04-21 15:10:28.108 | DEBUG    | meilisync.main:_:93 - progress={'master_log_file': 'binlog.000016', 'master_log_position': 157}
meilisync-meilisync-1    | ╭───────────────────── Traceback (most recent call last) ──────────────────────╮
meilisync-meilisync-1    | │ /meilisync/meilisync/main.py:129 in start                                    │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   126 │   │   lock = asyncio.Lock()                                          │
meilisync-meilisync-1    | │   127 │   │   await asyncio.gather(_(), interval())                          │
meilisync-meilisync-1    | │   128 │                                                                      │
meilisync-meilisync-1    | │ ❱ 129 │   asyncio.run(run())                                                 │
meilisync-meilisync-1    | │   130                                                                        │
meilisync-meilisync-1    | │   131                                                                        │
meilisync-meilisync-1    | │   132 @app.command(help="Delete all data in MeiliSearch and full sync")      │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │                _ = <function start.<locals>._ at 0x400674dca0>           │ │
meilisync-meilisync-1    | │ │       collection = <meilisync.event.EventCollection object at            │ │
meilisync-meilisync-1    | │ │                    0x400675e6d0>                                         │ │
meilisync-meilisync-1    | │ │          context = <click.core.Context object at 0x4006744e80>           │ │
meilisync-meilisync-1    | │ │ current_progress = {                                                     │ │
meilisync-meilisync-1    | │ │                    │   'master_log_file': 'binlog.000016',               │ │
meilisync-meilisync-1    | │ │                    │   'master_log_position': 157                        │ │
meilisync-meilisync-1    | │ │                    }                                                     │ │
meilisync-meilisync-1    | │ │         interval = <function start.<locals>.interval at 0x400674db80>    │ │
meilisync-meilisync-1    | │ │             lock = <asyncio.locks.Lock object at 0x400675e3a0            │ │
meilisync-meilisync-1    | │ │                    [unlocked]>                                           │ │
meilisync-meilisync-1    | │ │            meili = <meilisync.meili.Meili object at 0x400675e100>        │ │
meilisync-meilisync-1    | │ │   meili_settings = MeiliSearch(                                          │ │
meilisync-meilisync-1    | │ │                    │   api_url='http://meilisearch:7700',                │ │
meilisync-meilisync-1    | │ │                    │                                                     │ │
meilisync-meilisync-1    | │ │                    api_key='7TL351wVlGaszDQjfXGe7J6_15ZIARF0wa_zpwXaAOU… │ │
meilisync-meilisync-1    | │ │                    │   insert_size=1000,                                 │ │
meilisync-meilisync-1    | │ │                    │   insert_interval=10                                │ │
meilisync-meilisync-1    | │ │                    )                                                     │ │
meilisync-meilisync-1    | │ │         progress = <meilisync.progress.file.File object at 0x400675e490> │ │
meilisync-meilisync-1    | │ │              run = <function start.<locals>.run at 0x400674dc10>         │ │
meilisync-meilisync-1    | │ │         settings = Settings(                                             │ │
meilisync-meilisync-1    | │ │                    │   plugins=['meilisync.plugin.Plugin'],              │ │
meilisync-meilisync-1    | │ │                    │   progress=Progress(                                │ │
meilisync-meilisync-1    | │ │                    │   │   type=<ProgressType.file: 'file'>,             │ │
meilisync-meilisync-1    | │ │                    │   │   path='./progress.json'                        │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   debug=True,                                       │ │
meilisync-meilisync-1    | │ │                    │   source=Source(                                    │ │
meilisync-meilisync-1    | │ │                    │   │   type=<SourceType.mysql: 'mysql'>,             │ │
meilisync-meilisync-1    | │ │                    │   │   database='blog',                              │ │
meilisync-meilisync-1    | │ │                    │   │   port=3306,                                    │ │
meilisync-meilisync-1    | │ │                    │   │   host='mysql',                                 │ │
meilisync-meilisync-1    | │ │                    │   │   charset='utf8',                               │ │
meilisync-meilisync-1    | │ │                    │   │   password='meilisync',                         │ │
meilisync-meilisync-1    | │ │                    │   │   user='root'                                   │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   meilisearch=MeiliSearch(                          │ │
meilisync-meilisync-1    | │ │                    │   │   api_url='http://meilisearch:7700',            │ │
meilisync-meilisync-1    | │ │                    │   │                                                 │ │
meilisync-meilisync-1    | │ │                    api_key='7TL351wVlGaszDQjfXGe7J6_15ZIARF0wa_zpwXaAOU… │ │
meilisync-meilisync-1    | │ │                    │   │   insert_size=1000,                             │ │
meilisync-meilisync-1    | │ │                    │   │   insert_interval=10                            │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   sync=[                                            │ │
meilisync-meilisync-1    | │ │                    │   │   Sync(                                         │ │
meilisync-meilisync-1    | │ │                    │   │   │   plugins=None,                             │ │
meilisync-meilisync-1    | │ │                    │   │   │   table='authors',                          │ │
meilisync-meilisync-1    | │ │                    │   │   │   pk='id',                                  │ │
meilisync-meilisync-1    | │ │                    │   │   │   full=True,                                │ │
meilisync-meilisync-1    | │ │                    │   │   │   index=None,                               │ │
meilisync-meilisync-1    | │ │                    │   │   │   fields={                                  │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'id': None,                           │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'first_name': None,                   │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'last_name': None,                    │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'email': None                         │ │
meilisync-meilisync-1    | │ │                    │   │   │   }                                         │ │
meilisync-meilisync-1    | │ │                    │   │   )                                             │ │
meilisync-meilisync-1    | │ │                    │   ],                                                │ │
meilisync-meilisync-1    | │ │                    │   sentry=None                                       │ │
meilisync-meilisync-1    | │ │                    )                                                     │ │
meilisync-meilisync-1    | │ │           source = <meilisync.source.mysql.MySQL object at 0x400675e8e0> │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/asyncio/runners.py:44 in run                        │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   41 │   │   events.set_event_loop(loop)                                     │
meilisync-meilisync-1    | │   42 │   │   if debug is not None:                                           │
meilisync-meilisync-1    | │   43 │   │   │   loop.set_debug(debug)                                       │
meilisync-meilisync-1    | │ ❱ 44 │   │   return loop.run_until_complete(main)                            │
meilisync-meilisync-1    | │   45 │   finally:                                                            │
meilisync-meilisync-1    | │   46 │   │   try:                                                            │
meilisync-meilisync-1    | │   47 │   │   │   _cancel_all_tasks(loop)                                     │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭──────────────────────────────── locals ────────────────────────────────╮   │
meilisync-meilisync-1    | │ │ debug = None                                                           │   │
meilisync-meilisync-1    | │ │  loop = <_UnixSelectorEventLoop running=False closed=True debug=False> │   │
meilisync-meilisync-1    | │ │  main = <coroutine object start.<locals>.run at 0x4006705a40>          │   │
meilisync-meilisync-1    | │ ╰────────────────────────────────────────────────────────────────────────╯   │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/asyncio/base_events.py:647 in run_until_complete    │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │    644 │   │   if not future.done():                                         │
meilisync-meilisync-1    | │    645 │   │   │   raise RuntimeError('Event loop stopped before Future comp │
meilisync-meilisync-1    | │    646 │   │                                                                 │
meilisync-meilisync-1    | │ ❱  647 │   │   return future.result()                                        │
meilisync-meilisync-1    | │    648 │                                                                     │
meilisync-meilisync-1    | │    649 │   def stop(self):                                                   │
meilisync-meilisync-1    | │    650 │   │   """Stop running the event loop.                               │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │   future = <Task finished name='Task-4' coro=<start.<locals>.run() done, │ │
meilisync-meilisync-1    | │ │            defined at /meilisync/meilisync/main.py:124>                  │ │
meilisync-meilisync-1    | │ │            exception=LookupError('unknown encoding: utf8mb3')>           │ │
meilisync-meilisync-1    | │ │ new_task = True                                                          │ │
meilisync-meilisync-1    | │ │     self = <_UnixSelectorEventLoop running=False closed=True             │ │
meilisync-meilisync-1    | │ │            debug=False>                                                  │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /meilisync/meilisync/main.py:127 in run                                      │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   124 │   async def run():                                                   │
meilisync-meilisync-1    | │   125 │   │   nonlocal lock                                                  │
meilisync-meilisync-1    | │   126 │   │   lock = asyncio.Lock()                                          │
meilisync-meilisync-1    | │ ❱ 127 │   │   await asyncio.gather(_(), interval())                          │
meilisync-meilisync-1    | │   128 │                                                                      │
meilisync-meilisync-1    | │   129 │   asyncio.run(run())                                                 │
meilisync-meilisync-1    | │   130                                                                        │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────── locals ──────────────────────────────╮        │
meilisync-meilisync-1    | │ │        _ = <function start.<locals>._ at 0x400674dca0>            │        │
meilisync-meilisync-1    | │ │ interval = <function start.<locals>.interval at 0x400674db80>     │        │
meilisync-meilisync-1    | │ │     lock = <asyncio.locks.Lock object at 0x400675e3a0 [unlocked]> │        │
meilisync-meilisync-1    | │ ╰───────────────────────────────────────────────────────────────────╯        │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /meilisync/meilisync/main.py:91 in _                                         │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │    88 │   │   │   │   │   │   │   f'No data found for table "{settings.sourc │
meilisync-meilisync-1    | │    89 │   │   │   │   │   │   )                                              │
meilisync-meilisync-1    | │    90 │   │   logger.info(f'Start increment sync data from "{settings.source │
meilisync-meilisync-1    | │ ❱  91 │   │   async for event in source:                                     │
meilisync-meilisync-1    | │    92 │   │   │   if settings.debug:                                         │
meilisync-meilisync-1    | │    93 │   │   │   │   logger.debug(event)                                    │
meilisync-meilisync-1    | │    94 │   │   │   current_progress = event.progress                          │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │       collection = <meilisync.event.EventCollection object at            │ │
meilisync-meilisync-1    | │ │                    0x400675e6d0>                                         │ │
meilisync-meilisync-1    | │ │ current_progress = {                                                     │ │
meilisync-meilisync-1    | │ │                    │   'master_log_file': 'binlog.000016',               │ │
meilisync-meilisync-1    | │ │                    │   'master_log_position': 157                        │ │
meilisync-meilisync-1    | │ │                    }                                                     │ │
meilisync-meilisync-1    | │ │            event = ProgressEvent(                                        │ │
meilisync-meilisync-1    | │ │                    │   progress={                                        │ │
meilisync-meilisync-1    | │ │                    │   │   'master_log_file': 'binlog.000016',           │ │
meilisync-meilisync-1    | │ │                    │   │   'master_log_position': 157                    │ │
meilisync-meilisync-1    | │ │                    │   }                                                 │ │
meilisync-meilisync-1    | │ │                    )                                                     │ │
meilisync-meilisync-1    | │ │             lock = <asyncio.locks.Lock object at 0x400675e3a0            │ │
meilisync-meilisync-1    | │ │                    [unlocked]>                                           │ │
meilisync-meilisync-1    | │ │            meili = <meilisync.meili.Meili object at 0x400675e100>        │ │
meilisync-meilisync-1    | │ │   meili_settings = MeiliSearch(                                          │ │
meilisync-meilisync-1    | │ │                    │   api_url='http://meilisearch:7700',                │ │
meilisync-meilisync-1    | │ │                    │                                                     │ │
meilisync-meilisync-1    | │ │                    api_key='7TL351wVlGaszDQjfXGe7J6_15ZIARF0wa_zpwXaAOU… │ │
meilisync-meilisync-1    | │ │                    │   insert_size=1000,                                 │ │
meilisync-meilisync-1    | │ │                    │   insert_interval=10                                │ │
meilisync-meilisync-1    | │ │                    )                                                     │ │
meilisync-meilisync-1    | │ │         progress = <meilisync.progress.file.File object at 0x400675e490> │ │
meilisync-meilisync-1    | │ │         settings = Settings(                                             │ │
meilisync-meilisync-1    | │ │                    │   plugins=['meilisync.plugin.Plugin'],              │ │
meilisync-meilisync-1    | │ │                    │   progress=Progress(                                │ │
meilisync-meilisync-1    | │ │                    │   │   type=<ProgressType.file: 'file'>,             │ │
meilisync-meilisync-1    | │ │                    │   │   path='./progress.json'                        │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   debug=True,                                       │ │
meilisync-meilisync-1    | │ │                    │   source=Source(                                    │ │
meilisync-meilisync-1    | │ │                    │   │   type=<SourceType.mysql: 'mysql'>,             │ │
meilisync-meilisync-1    | │ │                    │   │   database='blog',                              │ │
meilisync-meilisync-1    | │ │                    │   │   port=3306,                                    │ │
meilisync-meilisync-1    | │ │                    │   │   host='mysql',                                 │ │
meilisync-meilisync-1    | │ │                    │   │   charset='utf8',                               │ │
meilisync-meilisync-1    | │ │                    │   │   password='meilisync',                         │ │
meilisync-meilisync-1    | │ │                    │   │   user='root'                                   │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   meilisearch=MeiliSearch(                          │ │
meilisync-meilisync-1    | │ │                    │   │   api_url='http://meilisearch:7700',            │ │
meilisync-meilisync-1    | │ │                    │   │                                                 │ │
meilisync-meilisync-1    | │ │                    api_key='7TL351wVlGaszDQjfXGe7J6_15ZIARF0wa_zpwXaAOU… │ │
meilisync-meilisync-1    | │ │                    │   │   insert_size=1000,                             │ │
meilisync-meilisync-1    | │ │                    │   │   insert_interval=10                            │ │
meilisync-meilisync-1    | │ │                    │   ),                                                │ │
meilisync-meilisync-1    | │ │                    │   sync=[                                            │ │
meilisync-meilisync-1    | │ │                    │   │   Sync(                                         │ │
meilisync-meilisync-1    | │ │                    │   │   │   plugins=None,                             │ │
meilisync-meilisync-1    | │ │                    │   │   │   table='authors',                          │ │
meilisync-meilisync-1    | │ │                    │   │   │   pk='id',                                  │ │
meilisync-meilisync-1    | │ │                    │   │   │   full=True,                                │ │
meilisync-meilisync-1    | │ │                    │   │   │   index=None,                               │ │
meilisync-meilisync-1    | │ │                    │   │   │   fields={                                  │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'id': None,                           │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'first_name': None,                   │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'last_name': None,                    │ │
meilisync-meilisync-1    | │ │                    │   │   │   │   'email': None                         │ │
meilisync-meilisync-1    | │ │                    │   │   │   }                                         │ │
meilisync-meilisync-1    | │ │                    │   │   )                                             │ │
meilisync-meilisync-1    | │ │                    │   ],                                                │ │
meilisync-meilisync-1    | │ │                    │   sentry=None                                       │ │
meilisync-meilisync-1    | │ │                    )                                                     │ │
meilisync-meilisync-1    | │ │           source = <meilisync.source.mysql.MySQL object at 0x400675e8e0> │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /meilisync/meilisync/source/mysql.py:89 in __aiter__                         │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │    86 │   │   async for event in stream:                                     │
meilisync-meilisync-1    | │    87 │   │   │   if isinstance(event, WriteRowsEvent):                      │
meilisync-meilisync-1    | │    88 │   │   │   │   event_type = EventType.create                          │
meilisync-meilisync-1    | │ ❱  89 │   │   │   │   data = event.rows[0]["values"]                         │
meilisync-meilisync-1    | │    90 │   │   │   elif isinstance(event, UpdateRowsEvent):                   │
meilisync-meilisync-1    | │    91 │   │   │   │   event_type = EventType.update                          │
meilisync-meilisync-1    | │    92 │   │   │   │   data = event.rows[0]["after_values"]                   │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │               event = <asyncmy.replication.row_events.WriteRowsEvent     │ │
meilisync-meilisync-1    | │ │                       object at 0x40177a5070>                            │ │
meilisync-meilisync-1    | │ │          event_type = <EventType.create: 'create'>                       │ │
meilisync-meilisync-1    | │ │     master_log_file = 'binlog.000016'                                    │ │
meilisync-meilisync-1    | │ │ master_log_position = 157                                                │ │
meilisync-meilisync-1    | │ │                self = <meilisync.source.mysql.MySQL object at            │ │
meilisync-meilisync-1    | │ │                       0x400675e8e0>                                      │ │
meilisync-meilisync-1    | │ │              stream = <asyncmy.replication.binlogstream.BinLogStream     │ │
meilisync-meilisync-1    | │ │                       object at 0x400676fa90>                            │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/site-packages/asyncmy/replication/row_events.py:453 │
meilisync-meilisync-1    | │ in rows                                                                      │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   450 │   @property                                                          │
meilisync-meilisync-1    | │   451 │   def rows(self):                                                    │
meilisync-meilisync-1    | │   452 │   │   if self._rows is None:                                         │
meilisync-meilisync-1    | │ ❱ 453 │   │   │   self._fetch_rows()                                         │
meilisync-meilisync-1    | │   454 │   │   return self._rows                                              │
meilisync-meilisync-1    | │   455                                                                        │
meilisync-meilisync-1    | │   456                                                                        │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │ self = <asyncmy.replication.row_events.WriteRowsEvent object at          │ │
meilisync-meilisync-1    | │ │        0x40177a5070>                                                     │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/site-packages/asyncmy/replication/row_events.py:448 │
meilisync-meilisync-1    | │ in _fetch_rows                                                               │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   445 │   │   │   return                                                     │
meilisync-meilisync-1    | │   446 │   │                                                                  │
meilisync-meilisync-1    | │   447 │   │   while self.packet.read_bytes < self.event_size:                │
meilisync-meilisync-1    | │ ❱ 448 │   │   │   self._rows.append(self._fetch_one_row())                   │
meilisync-meilisync-1    | │   449 │                                                                      │
meilisync-meilisync-1    | │   450 │   @property                                                          │
meilisync-meilisync-1    | │   451 │   def rows(self):                                                    │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │ self = <asyncmy.replication.row_events.WriteRowsEvent object at          │ │
meilisync-meilisync-1    | │ │        0x40177a5070>                                                     │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/site-packages/asyncmy/replication/row_events.py:489 │
meilisync-meilisync-1    | │ in _fetch_one_row                                                            │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   486 │   │   │   self.columns_present_bitmap = self.packet.read((self.numbe │
meilisync-meilisync-1    | │   487 │                                                                      │
meilisync-meilisync-1    | │   488 │   def _fetch_one_row(self):                                          │
meilisync-meilisync-1    | │ ❱ 489 │   │   return {"values": self._read_column_data(self.columns_present_ │
meilisync-meilisync-1    | │   490                                                                        │
meilisync-meilisync-1    | │   491                                                                        │
meilisync-meilisync-1    | │   492 class UpdateRowsEvent(RowsEvent):                                      │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │ self = <asyncmy.replication.row_events.WriteRowsEvent object at          │ │
meilisync-meilisync-1    | │ │        0x40177a5070>                                                     │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/site-packages/asyncmy/replication/row_events.py:162 │
meilisync-meilisync-1    | │ in _read_column_data                                                         │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   159 │   │   │   │   if column.max_length > 255:                            │
meilisync-meilisync-1    | │   160 │   │   │   │   │   values[name] = self.__read_string(2, column)       │
meilisync-meilisync-1    | │   161 │   │   │   │   else:                                                  │
meilisync-meilisync-1    | │ ❱ 162 │   │   │   │   │   values[name] = self.__read_string(1, column)       │
meilisync-meilisync-1    | │   163 │   │   │   elif column.type == NEWDECIMAL:                            │
meilisync-meilisync-1    | │   164 │   │   │   │   values[name] = self.__read_new_decimal(column)         │
meilisync-meilisync-1    | │   165 │   │   │   elif column.type == BLOB:                                  │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │       cols_bitmap = b'\xff'                                              │ │
meilisync-meilisync-1    | │ │            column = <asyncmy.replication.column.Column object at         │ │
meilisync-meilisync-1    | │ │                     0x400676ffa0>                                        │ │
meilisync-meilisync-1    | │ │                 i = 1                                                    │ │
meilisync-meilisync-1    | │ │              name = 'first_name'                                         │ │
meilisync-meilisync-1    | │ │        nb_columns = 6                                                    │ │
meilisync-meilisync-1    | │ │       null_bitmap = b'\x00'                                              │ │
meilisync-meilisync-1    | │ │ null_bitmap_index = 1                                                    │ │
meilisync-meilisync-1    | │ │              self = <asyncmy.replication.row_events.WriteRowsEvent       │ │
meilisync-meilisync-1    | │ │                     object at 0x40177a5070>                              │ │
meilisync-meilisync-1    | │ │          unsigned = False                                                │ │
meilisync-meilisync-1    | │ │            values = {'id': 101}                                          │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ /usr/local/lib/python3.9/site-packages/asyncmy/replication/row_events.py:251 │
meilisync-meilisync-1    | │ in __read_string                                                             │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │   248 │   │   string = self.packet.read_length_coded_pascal_string(size)     │
meilisync-meilisync-1    | │   249 │   │   if column.character_set_name is not None:                      │
meilisync-meilisync-1    | │   250 │   │   │   encoding = self.charset_to_encoding(column.character_set_n │
meilisync-meilisync-1    | │ ❱ 251 │   │   │   string = string.decode(encoding)                           │
meilisync-meilisync-1    | │   252 │   │   return string                                                  │
meilisync-meilisync-1    | │   253 │                                                                      │
meilisync-meilisync-1    | │   254 │   def __read_bit(self, column):                                      │
meilisync-meilisync-1    | │                                                                              │
meilisync-meilisync-1    | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-meilisync-1    | │ │   column = <asyncmy.replication.column.Column object at 0x400676ffa0>    │ │
meilisync-meilisync-1    | │ │ encoding = 'utf8mb3'                                                     │ │
meilisync-meilisync-1    | │ │     self = <asyncmy.replication.row_events.WriteRowsEvent object at      │ │
meilisync-meilisync-1    | │ │            0x40177a5070>                                                 │ │
meilisync-meilisync-1    | │ │     size = 1                                                             │ │
meilisync-meilisync-1    | │ │   string = b'Bruno'                                                      │ │
meilisync-meilisync-1    | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-meilisync-1    | ╰──────────────────────────────────────────────────────────────────────────────╯
meilisync-meilisync-1    | LookupError: unknown encoding: utf8mb3
meilisync-meilisync-1 exited with code 1

Serialization error when importing from MongoDB

Description

I tried to import documents with meilisync from the MongoDB free plan: https://cloud.mongodb.com/ and got a serialization error:
TypeError: Object of type ObjectId is not JSON serializable

Data format:

I used the sample data proposed by MongoDB which looked like:

{
  "_id": { "$oid": "64c91330c2a74205feef694b" },
  "account_id": { "$numberInt": "278603" },
  "limit": { "$numberInt": "10000" },
  "products": ["Commodity", "InvestmentStock"]
}
Screenshot 2023-08-01 at 16 14 50

Configuration file

debug: true
plugins:
  - meilisync.plugin.Plugin
progress:
  type: file
source:
  type: mongo
  host: mongodb+srv://cluster_IP.mongodb.net
  port: 27017
  username: userName
  password: passWord
  database: sample_analytics
meilisearch:
  api_url: http://localhost:7700/
  api_key: 'masterKey'
  insert_size: 100
  insert_interval: 10
sync:
  - table: accounts
    index: accounts
    full: true

Screenshots & Logs:

Screenshot 2023-08-01 at 15 08 48

I put my logs in a file just in case:
output_error.txt

Note:

I tried to remove full: true by:

    fields:
      account_id:
      limit:
      products:

But I received another error that I will describe in another issue.

RDS DB replication slot not released by meilisync

Hello,
I configured meilisync with meilisearch and Postgres RDS DB. Everything works ok, except that the replication slot on RDS is growing continuosely. Each 5 minutes it will increase 64MB, which is about 18GB per day. If I understand correctly, meilisync should consume data from DB replication slot, and empty it, but this doesn't happen.
I configured a maximum size value for the replication slot of 3GB (max_slot_wal_keep_size). When the replication slot reached this size it changed its status from active=true to active=false and meilisync app stopped syncing.
Could you please tell if there are some additional configurations that should be made in order to solve this issue?

pydantic.errors.PydanticImportError: `BaseSettings` has been moved

$ meilisync --help

Alamalinux 9.

LMK if you have any ideas on how to resolve -- thx

Traceback (most recent call last):
  File "/srv/proj/.venv/bin/meilisync", line 5, in <module>
    from meilisync.main import app
  File "/srv/proj/.venv/lib64/python3.9/site-packages/meilisync/main.py", line 8, in <module>
    from meilisync.discover import get_progress, get_source
  File "/srv/proj/.venv/lib64/python3.9/site-packages/meilisync/discover.py", line 7, in <module>
    from meilisync import progress, source
  File "/srv/proj/.venv/lib64/python3.9/site-packages/meilisync/source/__init__.py", line 4, in <module>
    from meilisync.settings import Sync
  File "/srv/proj/.venv/lib64/python3.9/site-packages/meilisync/settings.py", line 3, in <module>
    from pydantic import BaseModel, BaseSettings, Extra
  File "/srv/proj/.venv/lib64/python3.9/site-packages/pydantic/__init__.py", line 218, in __getattr__
    return _getattr_migration(attr_name)
  File "/srv/proj/.venv/lib64/python3.9/site-packages/pydantic/_migration.py", line 294, in wrapper
    raise PydanticImportError(
pydantic.errors.PydanticImportError: `BaseSettings` has been moved to the `pydantic-settings` package. See https://docs.pydantic.dev/2.4/migration/#basesettings-has-moved-to-pydantic-settings for more details.

Infinite restarts after startup

problem

When I run it with docker compose, only the first data is synchronized, and the subsequent data seems to be out of sync.
And meilisync keeps re-running.

mysql log

2023-08-03 15:45:50 2023-08-03T06:45:50.279983Z 63 [Note] Aborted connection 63 to db: 'test' user: 'root' host: '172.22.0.4' (Got an error reading communication packets)
2023-08-03 15:45:50 2023-08-03T06:45:50.280026Z 64 [Note] Aborted connection 64 to db: 'test' user: 'root' host: '172.22.0.4' (Got an error reading communication packets)

meilisync log

2023-08-03 15:37:46 2023-08-03 06:37:46.050 | DEBUG    | meilisync.main:_:33 - plugins=['meilisync.plugin.Plugin'] progress=Progress(type=<ProgressType.file: 'file'>) debug=True source=Source(type=<SourceType.mysql: 'mysql'>, database='test', user='root', host='mysql', password='1234', port=3306) meilisearch=MeiliSearch(api_url='http://meilisearch:7700', api_key='W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8', insert_size=1000, insert_interval=10) sync=[Sync(plugins=['meilisync.plugin.Plugin'], table='user', pk='id', full=True, index='user-collections', fields={'id': None, 'name': None, 'age': None, 'email': None})] sentry=None
2023-08-03 15:37:46 2023-08-03 06:37:46.092 | INFO     | meilisync.main:_:82 - Full data sync for table "test.user" done! 2 documents added.
2023-08-03 15:37:46 2023-08-03 06:37:46.092 | INFO     | meilisync.main:_:90 - Start increment sync data from "mysql" to MeiliSearch...
2023-08-03 15:37:46 ╭───────────────────── Traceback (most recent call last) ──────────────────────╮
2023-08-03 15:37:46 │ /meilisync/meilisync/main.py:129 in start                                    │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │   126 │   │   lock = asyncio.Lock()                                          │
2023-08-03 15:37:46 │   127 │   │   await asyncio.gather(_(), interval())                          │
2023-08-03 15:37:46 │   128 │                                                                      │
2023-08-03 15:37:46 │ ❱ 129 │   asyncio.run(run())                                                 │
2023-08-03 15:37:46 │   130                                                                        │
2023-08-03 15:37:46 │   131                                                                        │
2023-08-03 15:37:46 │   132 @app.command(help="Delete all data in MeiliSearch and full sync")      │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
2023-08-03 15:37:46 │ │                _ = <function start.<locals>._ at 0x7f2f009abca0>         │ │
2023-08-03 15:37:46 │ │       collection = <meilisync.event.EventCollection object at            │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d6d0>                                       │ │
2023-08-03 15:37:46 │ │          context = <click.core.Context object at 0x7f2f009a3e80>         │ │
2023-08-03 15:37:46 │ │ current_progress = None                                                  │ │
2023-08-03 15:37:46 │ │         interval = <function start.<locals>.interval at 0x7f2f009abb80>  │ │
2023-08-03 15:37:46 │ │             lock = <asyncio.locks.Lock object at 0x7f2f0093d3a0          │ │
2023-08-03 15:37:46 │ │                    [unlocked]>                                           │ │
2023-08-03 15:37:46 │ │            meili = <meilisync.meili.Meili object at 0x7f2f0093d3d0>      │ │
2023-08-03 15:37:46 │ │   meili_settings = MeiliSearch(                                          │ │
2023-08-03 15:37:46 │ │                    │   api_url='http://meilisearch:7700',                │ │
2023-08-03 15:37:46 │ │                    │                                                     │ │
2023-08-03 15:37:46 │ │                    api_key='W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8… │ │
2023-08-03 15:37:46 │ │                    │   insert_size=1000,                                 │ │
2023-08-03 15:37:46 │ │                    │   insert_interval=10                                │ │
2023-08-03 15:37:46 │ │                    )                                                     │ │
2023-08-03 15:37:46 │ │         progress = <meilisync.progress.file.File object at               │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d490>                                       │ │
2023-08-03 15:37:46 │ │              run = <function start.<locals>.run at 0x7f2f009abc10>       │ │
2023-08-03 15:37:46 │ │         settings = Settings(                                             │ │
2023-08-03 15:37:46 │ │                    │   plugins=['meilisync.plugin.Plugin'],              │ │
2023-08-03 15:37:46 │ │                    │   progress=Progress(                                │ │
2023-08-03 15:37:46 │ │                    │   │   type=<ProgressType.file: 'file'>              │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   debug=True,                                       │ │
2023-08-03 15:37:46 │ │                    │   source=Source(                                    │ │
2023-08-03 15:37:46 │ │                    │   │   type=<SourceType.mysql: 'mysql'>,             │ │
2023-08-03 15:37:46 │ │                    │   │   database='test',                              │ │
2023-08-03 15:37:46 │ │                    │   │   user='root',                                  │ │
2023-08-03 15:37:46 │ │                    │   │   host='mysql',                                 │ │
2023-08-03 15:37:46 │ │                    │   │   password='1234',                              │ │
2023-08-03 15:37:46 │ │                    │   │   port=3306                                     │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   meilisearch=MeiliSearch(                          │ │
2023-08-03 15:37:46 │ │                    │   │   api_url='http://meilisearch:7700',            │ │
2023-08-03 15:37:46 │ │                    │   │                                                 │ │
2023-08-03 15:37:46 │ │                    api_key='W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8… │ │
2023-08-03 15:37:46 │ │                    │   │   insert_size=1000,                             │ │
2023-08-03 15:37:46 │ │                    │   │   insert_interval=10                            │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   sync=[                                            │ │
2023-08-03 15:37:46 │ │                    │   │   Sync(                                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   plugins=['meilisync.plugin.Plugin'],      │ │
2023-08-03 15:37:46 │ │                    │   │   │   table='user',                             │ │
2023-08-03 15:37:46 │ │                    │   │   │   pk='id',                                  │ │
2023-08-03 15:37:46 │ │                    │   │   │   full=True,                                │ │
2023-08-03 15:37:46 │ │                    │   │   │   index='user-collections',                 │ │
2023-08-03 15:37:46 │ │                    │   │   │   fields={                                  │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'id': None,                           │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'name': None,                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'age': None,                          │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'email': None                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   }                                         │ │
2023-08-03 15:37:46 │ │                    │   │   )                                             │ │
2023-08-03 15:37:46 │ │                    │   ],                                                │ │
2023-08-03 15:37:46 │ │                    │   sentry=None                                       │ │
2023-08-03 15:37:46 │ │                    )                                                     │ │
2023-08-03 15:37:46 │ │           source = <meilisync.source.mysql.MySQL object at               │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d8e0>                                       │ │
2023-08-03 15:37:46 │ ╰──────────────────────────────────────────────────────────────────────────╯ │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /usr/local/lib/python3.9/asyncio/runners.py:44 in run                        │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │   41 │   │   events.set_event_loop(loop)                                     │
2023-08-03 15:37:46 │   42 │   │   if debug is not None:                                           │
2023-08-03 15:37:46 │   43 │   │   │   loop.set_debug(debug)                                       │
2023-08-03 15:37:46 │ ❱ 44 │   │   return loop.run_until_complete(main)                            │
2023-08-03 15:37:46 │   45 │   finally:                                                            │
2023-08-03 15:37:46 │   46 │   │   try:                                                            │
2023-08-03 15:37:46 │   47 │   │   │   _cancel_all_tasks(loop)                                     │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭──────────────────────────────── locals ────────────────────────────────╮   │
2023-08-03 15:37:46 │ │ debug = None                                                           │   │
2023-08-03 15:37:46 │ │  loop = <_UnixSelectorEventLoop running=False closed=True debug=False> │   │
2023-08-03 15:37:46 │ │  main = <coroutine object start.<locals>.run at 0x7f2f009e3a40>        │   │
2023-08-03 15:37:46 │ ╰────────────────────────────────────────────────────────────────────────╯   │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /usr/local/lib/python3.9/asyncio/base_events.py:647 in run_until_complete    │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │    644 │   │   if not future.done():                                         │
2023-08-03 15:37:46 │    645 │   │   │   raise RuntimeError('Event loop stopped before Future comp │
2023-08-03 15:37:46 │    646 │   │                                                                 │
2023-08-03 15:37:46 │ ❱  647 │   │   return future.result()                                        │
2023-08-03 15:37:46 │    648 │                                                                     │
2023-08-03 15:37:46 │    649 │   def stop(self):                                                   │
2023-08-03 15:37:46 │    650 │   │   """Stop running the event loop.                               │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
2023-08-03 15:37:46 │ │   future = <Task finished name='Task-4' coro=<start.<locals>.run() done, │ │
2023-08-03 15:37:46 │ │            defined at /meilisync/meilisync/main.py:124>                  │ │
2023-08-03 15:37:46 │ │            exception=TypeError("'NoneType' object is not                 │ │
2023-08-03 15:37:46 │ │            subscriptable")>                                              │ │
2023-08-03 15:37:46 │ │ new_task = True                                                          │ │
2023-08-03 15:37:46 │ │     self = <_UnixSelectorEventLoop running=False closed=True             │ │
2023-08-03 15:37:46 │ │            debug=False>                                                  │ │
2023-08-03 15:37:46 │ ╰──────────────────────────────────────────────────────────────────────────╯ │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /meilisync/meilisync/main.py:127 in run                                      │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │   124 │   async def run():                                                   │
2023-08-03 15:37:46 │   125 │   │   nonlocal lock                                                  │
2023-08-03 15:37:46 │   126 │   │   lock = asyncio.Lock()                                          │
2023-08-03 15:37:46 │ ❱ 127 │   │   await asyncio.gather(_(), interval())                          │
2023-08-03 15:37:46 │   128 │                                                                      │
2023-08-03 15:37:46 │   129 │   asyncio.run(run())                                                 │
2023-08-03 15:37:46 │   130                                                                        │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭────────────────────────────── locals ───────────────────────────────╮      │
2023-08-03 15:37:46 │ │        _ = <function start.<locals>._ at 0x7f2f009abca0>            │      │
2023-08-03 15:37:46 │ │ interval = <function start.<locals>.interval at 0x7f2f009abb80>     │      │
2023-08-03 15:37:46 │ │     lock = <asyncio.locks.Lock object at 0x7f2f0093d3a0 [unlocked]> │      │
2023-08-03 15:37:46 │ ╰─────────────────────────────────────────────────────────────────────╯      │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /meilisync/meilisync/main.py:91 in _                                         │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │    88 │   │   │   │   │   │   │   f'No data found for table "{settings.sourc │
2023-08-03 15:37:46 │    89 │   │   │   │   │   │   )                                              │
2023-08-03 15:37:46 │    90 │   │   logger.info(f'Start increment sync data from "{settings.source │
2023-08-03 15:37:46 │ ❱  91 │   │   async for event in source:                                     │
2023-08-03 15:37:46 │    92 │   │   │   if settings.debug:                                         │
2023-08-03 15:37:46 │    93 │   │   │   │   logger.debug(event)                                    │
2023-08-03 15:37:46 │    94 │   │   │   current_progress = event.progress                          │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
2023-08-03 15:37:46 │ │       collection = <meilisync.event.EventCollection object at            │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d6d0>                                       │ │
2023-08-03 15:37:46 │ │ current_progress = None                                                  │ │
2023-08-03 15:37:46 │ │             data = [                                                     │ │
2023-08-03 15:37:46 │ │                    │   {                                                 │ │
2023-08-03 15:37:46 │ │                    │   │   'id': 1,                                      │ │
2023-08-03 15:37:46 │ │                    │   │   'name': 'kim',                                │ │
2023-08-03 15:37:46 │ │                    │   │   'age': 11,                                    │ │
2023-08-03 15:37:46 │ │                    │   │   'email': '[email protected]'                        │ │
2023-08-03 15:37:46 │ │                    │   },                                                │ │
2023-08-03 15:37:46 │ │                    │   {                                                 │ │
2023-08-03 15:37:46 │ │                    │   │   'id': 2,                                      │ │
2023-08-03 15:37:46 │ │                    │   │   'name': 'lee',                                │ │
2023-08-03 15:37:46 │ │                    │   │   'age': 2,                                     │ │
2023-08-03 15:37:46 │ │                    │   │   'email': '[email protected]'                      │ │
2023-08-03 15:37:46 │ │                    │   }                                                 │ │
2023-08-03 15:37:46 │ │                    ]                                                     │ │
2023-08-03 15:37:46 │ │             lock = <asyncio.locks.Lock object at 0x7f2f0093d3a0          │ │
2023-08-03 15:37:46 │ │                    [unlocked]>                                           │ │
2023-08-03 15:37:46 │ │            meili = <meilisync.meili.Meili object at 0x7f2f0093d3d0>      │ │
2023-08-03 15:37:46 │ │   meili_settings = MeiliSearch(                                          │ │
2023-08-03 15:37:46 │ │                    │   api_url='http://meilisearch:7700',                │ │
2023-08-03 15:37:46 │ │                    │                                                     │ │
2023-08-03 15:37:46 │ │                    api_key='W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8… │ │
2023-08-03 15:37:46 │ │                    │   insert_size=1000,                                 │ │
2023-08-03 15:37:46 │ │                    │   insert_interval=10                                │ │
2023-08-03 15:37:46 │ │                    )                                                     │ │
2023-08-03 15:37:46 │ │         progress = <meilisync.progress.file.File object at               │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d490>                                       │ │
2023-08-03 15:37:46 │ │         settings = Settings(                                             │ │
2023-08-03 15:37:46 │ │                    │   plugins=['meilisync.plugin.Plugin'],              │ │
2023-08-03 15:37:46 │ │                    │   progress=Progress(                                │ │
2023-08-03 15:37:46 │ │                    │   │   type=<ProgressType.file: 'file'>              │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   debug=True,                                       │ │
2023-08-03 15:37:46 │ │                    │   source=Source(                                    │ │
2023-08-03 15:37:46 │ │                    │   │   type=<SourceType.mysql: 'mysql'>,             │ │
2023-08-03 15:37:46 │ │                    │   │   database='test',                              │ │
2023-08-03 15:37:46 │ │                    │   │   user='root',                                  │ │
2023-08-03 15:37:46 │ │                    │   │   host='mysql',                                 │ │
2023-08-03 15:37:46 │ │                    │   │   password='1234',                              │ │
2023-08-03 15:37:46 │ │                    │   │   port=3306                                     │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   meilisearch=MeiliSearch(                          │ │
2023-08-03 15:37:46 │ │                    │   │   api_url='http://meilisearch:7700',            │ │
2023-08-03 15:37:46 │ │                    │   │                                                 │ │
2023-08-03 15:37:46 │ │                    api_key='W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8… │ │
2023-08-03 15:37:46 │ │                    │   │   insert_size=1000,                             │ │
2023-08-03 15:37:46 │ │                    │   │   insert_interval=10                            │ │
2023-08-03 15:37:46 │ │                    │   ),                                                │ │
2023-08-03 15:37:46 │ │                    │   sync=[                                            │ │
2023-08-03 15:37:46 │ │                    │   │   Sync(                                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   plugins=['meilisync.plugin.Plugin'],      │ │
2023-08-03 15:37:46 │ │                    │   │   │   table='user',                             │ │
2023-08-03 15:37:46 │ │                    │   │   │   pk='id',                                  │ │
2023-08-03 15:37:46 │ │                    │   │   │   full=True,                                │ │
2023-08-03 15:37:46 │ │                    │   │   │   index='user-collections',                 │ │
2023-08-03 15:37:46 │ │                    │   │   │   fields={                                  │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'id': None,                           │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'name': None,                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'age': None,                          │ │
2023-08-03 15:37:46 │ │                    │   │   │   │   'email': None                         │ │
2023-08-03 15:37:46 │ │                    │   │   │   }                                         │ │
2023-08-03 15:37:46 │ │                    │   │   )                                             │ │
2023-08-03 15:37:46 │ │                    │   ],                                                │ │
2023-08-03 15:37:46 │ │                    │   sentry=None                                       │ │
2023-08-03 15:37:46 │ │                    )                                                     │ │
2023-08-03 15:37:46 │ │           source = <meilisync.source.mysql.MySQL object at               │ │
2023-08-03 15:37:46 │ │                    0x7f2f0093d8e0>                                       │ │
2023-08-03 15:37:46 │ │             sync = Sync(                                                 │ │
2023-08-03 15:37:46 │ │                    │   plugins=['meilisync.plugin.Plugin'],              │ │
2023-08-03 15:37:46 │ │                    │   table='user',                                     │ │
2023-08-03 15:37:46 │ │                    │   pk='id',                                          │ │
2023-08-03 15:37:46 │ │                    │   full=True,                                        │ │
2023-08-03 15:37:46 │ │                    │   index='user-collections',                         │ │
2023-08-03 15:37:46 │ │                    │   fields={                                          │ │
2023-08-03 15:37:46 │ │                    │   │   'id': None,                                   │ │
2023-08-03 15:37:46 │ │                    │   │   'name': None,                                 │ │
2023-08-03 15:37:46 │ │                    │   │   'age': None,                                  │ │
2023-08-03 15:37:46 │ │                    │   │   'email': None                                 │ │
2023-08-03 15:37:46 │ │                    │   }                                                 │ │
2023-08-03 15:37:46 │ │                    )                                                     │ │
2023-08-03 15:37:46 │ ╰──────────────────────────────────────────────────────────────────────────╯ │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /meilisync/meilisync/source/mysql.py:67 in __aiter__                         │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │    64 │   │   │   master_log_file = self.progress["master_log_file"]         │
2023-08-03 15:37:46 │    65 │   │   │   master_log_position = int(self.progress["master_log_positi │
2023-08-03 15:37:46 │    66 │   │   else:                                                          │
2023-08-03 15:37:46 │ ❱  67 │   │   │   master_log_file, master_log_position = await self._get_bin │
2023-08-03 15:37:46 │    68 │   │   yield ProgressEvent(                                           │
2023-08-03 15:37:46 │    69 │   │   │   progress={                                                 │
2023-08-03 15:37:46 │    70 │   │   │   │   "master_log_file": master_log_file,                    │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭──────────────────────────── locals ────────────────────────────╮           │
2023-08-03 15:37:46 │ │ self = <meilisync.source.mysql.MySQL object at 0x7f2f0093d8e0> │           │
2023-08-03 15:37:46 │ ╰────────────────────────────────────────────────────────────────╯           │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ /meilisync/meilisync/source/mysql.py:58 in _get_binlog_position              │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │    55 │   │   async with self.conn.cursor(cursor=DictCursor) as cur:         │
2023-08-03 15:37:46 │    56 │   │   │   await cur.execute("SHOW MASTER STATUS")                    │
2023-08-03 15:37:46 │    57 │   │   │   ret = await cur.fetchone()                                 │
2023-08-03 15:37:46 │ ❱  58 │   │   │   return ret["File"], ret["Position"]                        │
2023-08-03 15:37:46 │    59 │                                                                      │
2023-08-03 15:37:46 │    60 │   async def __aiter__(self):                                         │
2023-08-03 15:37:46 │    61 │   │   self.conn = await asyncmy.connect(**self.kwargs)               │
2023-08-03 15:37:46 │                                                                              │
2023-08-03 15:37:46 │ ╭──────────────────────────── locals ────────────────────────────╮           │
2023-08-03 15:37:46 │ │  cur = <asyncmy.cursors.DictCursor object at 0x7f2f000ef160>   │           │
2023-08-03 15:37:46 │ │  ret = None                                                    │           │
2023-08-03 15:37:46 │ │ self = <meilisync.source.mysql.MySQL object at 0x7f2f0093d8e0> │           │
2023-08-03 15:37:46 │ ╰────────────────────────────────────────────────────────────────╯           │
2023-08-03 15:37:46 ╰──────────────────────────────────────────────────────────────────────────────╯
2023-08-03 15:37:46 TypeError: 'NoneType' object is not subscriptable

docker-compose.yaml

version: '3'

services:
  mysql:
    image: mysql:5
    container_name: mysql
    restart: always
    platform: linux/amd64
    environment:
      MYSQL_ROOT_PASSWORD: "1234"
      MYSQL_DATABASE: test
      MYSQL_USER: kim
      MYSQL_PASSWORD: "1234"
    healthcheck:
      test:
        ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p1234"]
      interval: 30s
      timeout: 10s
      retries: 5
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - 3306:3306

  meilisearch:
    container_name: meilisearch
    image: getmeili/meilisearch:v1.2
    environment:
      - http_proxy
      - https_proxy
      - MEILI_MASTER_KEY=W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8
      # - MEILI_MASTER_KEY=${MEILI_MASTER_KEY:-masterKey}
      - MEILI_NO_ANALYTICS=${MEILI_NO_ANALYTICS:-true}
      - MEILI_ENV=${MEILI_ENV:-development}
      - MEILI_LOG_LEVEL
      - MEILI_DB_PATH=${MEILI_DB_PATH:-/data.ms}
    ports:
      - ${MEILI_PORT:-7700}:7700
    volumes:
      - ./data.ms:/data.ms
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy

  meilisync:
    image: long2ice/meilisync
    volumes:
      - ./config.yml:/meilisync/config.yml
    restart: always
    depends_on:
      - meilisearch

config.yaml

debug: true
plugins:
  - meilisync.plugin.Plugin
progress:
  type: file
source:
  type: mysql
  host: mysql
  port: 3306
  user: root
  password: "1234"
  database: test
meilisearch:
  api_url: http://meilisearch:7700
  api_key: W_R0FiptNQ6f6CKCZPoGNGw4UuJfXc6j-B4YcVxwDw8
  insert_size: 1000
  insert_interval: 10
sync:
  - table: user
    index: user-collections
    plugins:
      - meilisync.plugin.Plugin
    full: true
    fields:
      id:
      name:
      age:
      email:
# sentry:
#   dsn: ""
#   environment: "production"

required argument is not an integer

When I update a JSON column in my MySQL database I get:

error: required argument is not an integer

The issue seems to be related to how the asyncmy library handles JSON columns in MySQL replication events. When the UpdateRowsEvent is processed, and the asyncmy library attempts to read the value of my languages column, which is a JSON column, it encounters an error.

But I can't really figure how to handle JSON column updates?

Am I missing something here?

TypeError: 'async for' requires an object with __aiter__ method, got list

here my docker-compose

meilisearch:
api_url: http://MY_IP:7700/
api_key: MY_API_KEY
insert_size: 1000
insert_interval: 10

source:
type: mysql
host: MY_IP
port: 3307
database: MY_DB
user: LOGIN
password: PASSWORD

sync:

  • table: articles
    index: MEILISEARCH_INDEX_NAME_1
    full: true
    pk: id

progress:
type: file

Postgres source ignores changes

The following lines are used to determine whether to add a change to the queue.
Instead of return when the table is not in self.tables, it should be continue.
Otherwise if the first change's table is not in the list of tables, it will ignore all other changes.

for change in changes:
kind = change.get("kind")
table = change.get("table")
if table not in self.tables:
return

For example if self.tables = ["bar"], then the following replication message would be ignored:

changes = [
    {
        "kind": "update",
        "schema": "public",
        "table": "foo",
        "columnnames": ["id", "attributes"],
        "columntypes": ["integer", "hstore"],
        "columnvalues": [123456, '"foo"=>"bar"'],
        "oldkeys": {"keynames": ["id"], "keytypes": ["integer"], "keyvalues": [123456]},
    },
    {
        "kind": "update",
        "schema": "public",
        "table": "bar",
        "columnnames": ["id", "content"],
        "columntypes": ["integer", "text"],
        "columnvalues": [456789, "Lorem ipsum"],
        "oldkeys": {"keynames": ["id"], "keytypes": ["integer"], "keyvalues": [456789]},
    },
]

Meilisync loses event when both create and update occur for the same primary key in an interval

If I have an interval of, let's suppose, 10 seconds and insert size of 1000 documents, and within that interval, a create event is first received followed by an update event for the same primary key, the current implementation of Meilisync's event buffer leads to the create event being overwritten by the update event.

def add_event(self, sync: Sync, event: Event):
pk = event.data[sync.pk]
self._events.setdefault(sync, {})
self._events[sync][pk] = event

self._events[sync][pk] = event here the event will get overwritten.

A potential solution could involve modifying the event handling logic to append events rather than overwrite them, for instance:

self._events[sync][pk].append(event)

Or we could just do:

self._events[sync].append(event)

Does sync work with Google Cloud SQL Postgres ?

Hi!
I'm trying to setup sync with Postgres hosted by Google Cloud, currently my sync only copies names of the tables, but the contents of index is empty.
I see that meilisync requires wal2json extension for Postgres sync, but I don't see it as a supported extension for google cloud SQL: https://cloud.google.com/sql/docs/postgres/extensions#miscellaneous-extensions

I'm wondering is there an alternative solution, maybe some other extension will help me sync? or a setting in meilisync to use other form of decoding ...

Thanks!

Data not sync when data from DB changed

When I start meilisync, it show start increment sync data but when i make change from source table then nothing change to meilisearch. I not set insert_size and insert_interval so it can sync immediately. My postgres version is 14 and meilisearch 1.4.0.

Lost Connection During Query / Attribute Error: `NoneType` object has no attribute `write`

Hi, I'm encountering difficulties with getting Meilisync to function properly due to a recurring issue of lost connection, accompanied by an error upon attempting to reconnect. I'm utilizing MySQL as the data source for Meilisync.

Here's what I've observed:

  • The ctl_conn of the binlogstream is consistently lost after a short period.
  • Whenever a database change is made, an exception is raised: asyncmy.errors.OperationalError: (2013, 'Lost connection to MySQL server during query').
  • Despite attempts to recreate the binlog stream upon encountering this exception, it seems that the self.ctl_conn.connect() line fails to execute as expected.
  • Subsequently, an additional exception is triggered: AttributeError: 'NoneType' object has no attribute 'write', indicating that the self._writer attribute of the connection remains None even after connect() is invoked, which should not be the case.

I managed to make a workound by moving self.ctl_conn initialization inside the _create_stream function on the mysql source class (i would happily make a PR with this change if necessary):

    async def _create_stream(self):
        self.ctl_conn = await asyncmy.connect(**self.kwargs)
        await self.ctl_conn.connect()
        self.stream = BinLogStream(
            self.conn,
            self.ctl_conn,
            server_id=self.server_id,
            master_log_file=self.progress["master_log_file"],
            master_log_position=int(self.progress["master_log_position"]),
            resume_stream=True,
            blocking=True,
            only_schemas=[self.database],
            only_tables=[f"{self.database}.{table}" for table in self.tables],
            only_events=[WriteRowsEvent, UpdateRowsEvent, DeleteRowsEvent],
        )

    async def __aiter__(self):
        self.conn = await asyncmy.connect(**self.kwargs)
        if not self.progress:
            self.progress = await self.get_current_progress()
        yield ProgressEvent(
            progress=self.progress,
        )
        await self._create_stream()
                while True:
            try:
                async for event in self.stream:
                    ..:
            except OperationalError as e:
                logger.exception(f"Binlog stream error: {e}, sleep 10s and retry...")
                await asyncio.sleep(10)
                try:
                    await self.stream.close()
                    self.ctl_conn.close()
                    await self._create_stream()
                except Exception as e:
                    logger.exception(f"Recreate binlog stream error: {e}")

This adjustment resolves the reconnection issue, yet I aim to discover a method to maintain the connection alive to prevent recurrent OperationalError exception.

Here's the log of the OperationalError

2024-02-06 20:15:59.072 | ERROR    | meilisync.source.mysql:__aiter__:130 - Binlog stream error: (2013, 'Lost connection to MySQL server during query'), sleep 10s and retry...
Traceback (most recent call last):

  File "/usr/local/bin/meilisync", line 6, in <module>
    sys.exit(app())
    │   │    └ <typer.main.Typer object at 0x7f991300ba70>
    │   └ <built-in function exit>
    └ <module 'sys' (built-in)>
  File "/usr/local/lib/python3.12/site-packages/typer/main.py", line 311, in __call__
    return get_command(self)(*args, **kwargs)
           │           │      │       └ {}
           │           │      └ ()
           │           └ <typer.main.Typer object at 0x7f991300ba70>
           └ <function get_command at 0x7f9911e26340>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ ()
           │    └ <function TyperGroup.main at 0x7f9911e24c20>
           └ <TyperGroup callback>
  File "/usr/local/lib/python3.12/site-packages/typer/core.py", line 778, in main
    return _main(
           └ <function _main at 0x7f9911e1fb00>
  File "/usr/local/lib/python3.12/site-packages/typer/core.py", line 216, in _main
    rv = self.invoke(ctx)
         │    │      └ <click.core.Context object at 0x7f990edf7c20>
         │    └ <function MultiCommand.invoke at 0x7f99122447c0>
         └ <TyperGroup callback>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
           │               │       │       │      └ <click.core.Context object at 0x7f990fe15040>
           │               │       │       └ <function Command.invoke at 0x7f9912244180>
           │               │       └ <TyperCommand start>
           │               └ <click.core.Context object at 0x7f990fe15040>
           └ <function MultiCommand.invoke.<locals>._process_result at 0x7f990e544040>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           │   │      │    │           │   └ {}
           │   │      │    │           └ <click.core.Context object at 0x7f990fe15040>
           │   │      │    └ <function start at 0x7f990e53a020>
           │   │      └ <TyperCommand start>
           │   └ <function Context.invoke at 0x7f9912222ac0>
           └ <click.core.Context object at 0x7f990fe15040>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
                       │       └ {}
                       └ ()
  File "/usr/local/lib/python3.12/site-packages/typer/main.py", line 683, in wrapper
    return callback(**use_params)  # type: ignore
           │          └ {'context': <click.core.Context object at 0x7f990fe15040>}
           └ <function start at 0x7f990e539e40>

  File "/meilisync/meilisync/main.py", line 155, in start
    asyncio.run(run())
    │       │   └ <function start.<locals>.run at 0x7f990e51d3a0>
    │       └ <function run at 0x7f9912dc5e40>
    └ <module 'asyncio' from '/usr/local/lib/python3.12/asyncio/__init__.py'>

  File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           │      │   └ <coroutine object start.<locals>.run at 0x7f990e512ea0>
           │      └ <function Runner.run at 0x7f991237bce0>
           └ <asyncio.runners.Runner object at 0x7f990e51a300>
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           │    │     │                  └ <Task pending name='Task-4' coro=<start.<locals>.run() running at /meilisync/meilisync/main.py:153> wait_for=<_GatheringFutur...
           │    │     └ <function BaseEventLoop.run_until_complete at 0x7f9912379940>
           │    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
           └ <asyncio.runners.Runner object at 0x7f990e51a300>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 671, in run_until_complete
    self.run_forever()
    │    └ <function BaseEventLoop.run_forever at 0x7f99123798a0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 638, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x7f991237b6a0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1971, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x7f99124d9b20>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>
  File "/usr/local/lib/python3.12/asyncio/events.py", line 84, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>

  File "/meilisync/meilisync/main.py", line 102, in _
    async for event in source:
              │        └ <meilisync.source.mysql.MySQL object at 0x7f990e9523c0>
              └ Event(progress={'master_log_file': 'mysql-bin-changelog.492584', 'master_log_position': 3540744}, type=<EventType.update: 'up...

> File "/meilisync/meilisync/source/mysql.py", line 108, in __aiter__
    async for event in self.stream:
              │        │    └ <asyncmy.replication.binlogstream.BinLogStream object at 0x7f990e52e5a0>
              │        └ <meilisync.source.mysql.MySQL object at 0x7f990e9523c0>
              └ <asyncmy.replication.row_events.UpdateRowsEvent object at 0x7f990e56c110>

  File "/usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py", line 374, in __anext__
    ret = await self._read()
                │    └ <function BinLogStream._read at 0x7f990fe5eac0>
                └ <asyncmy.replication.binlogstream.BinLogStream object at 0x7f990e52e5a0>
  File "/usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py", line 310, in _read
    await binlog_event.init()
          │            └ <function BinLogPacket.init at 0x7f990fe0bb00>
          └ <asyncmy.replication.packets.BinLogPacket object at 0x7f991021eff0>
  File "/usr/local/lib/python3.12/site-packages/asyncmy/replication/packets.py", line 140, in init
    self.event and await self.event.init()
    │    │               │    │     └ <function TableMapEvent.init at 0x7f990fe5d260>
    │    │               │    └ <asyncmy.replication.row_events.TableMapEvent object at 0x7f990e56d6d0>
    │    │               └ <asyncmy.replication.packets.BinLogPacket object at 0x7f991021eff0>
    │    └ <asyncmy.replication.row_events.TableMapEvent object at 0x7f990e56d6d0>
    └ <asyncmy.replication.packets.BinLogPacket object at 0x7f991021eff0>
  File "/usr/local/lib/python3.12/site-packages/asyncmy/replication/row_events.py", line 580, in init
    await self._connection._get_table_information(self.schema, self.table_name)
          │    │           │                      │    │       │    └ 'test_courses'
          │    │           │                      │    │       └ <asyncmy.replication.row_events.TableMapEvent object at 0x7f990e56d6d0>
          │    │           │                      │    └ 'test_schema'
          │    │           │                      └ <asyncmy.replication.row_events.TableMapEvent object at 0x7f990e56d6d0>
          │    │           └ <bound method BinLogStream._get_table_information of <asyncmy.replication.binlogstream.BinLogStream object at 0x7f990e52e5a0>>
          │    └ <asyncmy.connection.Connection object at 0x7f990e52f8c0>
          └ <asyncmy.replication.row_events.TableMapEvent object at 0x7f990e56d6d0>
  File "/usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py", line 350, in _get_table_information
    await cursor.execute(
          │      └ <cyfunction Cursor.execute at 0x7f990fe07440>
          └ <asyncmy.cursors.DictCursor object at 0x7f990e97d760>
  File "asyncmy/cursors.pyx", line 179, in execute
    result = await self._query(query)
  File "asyncmy/cursors.pyx", line 364, in _query
    await conn.query(q)
  File "asyncmy/connection.pyx", line 494, in query
    await self._read_query_result(unbuffered=unbuffered)
  File "asyncmy/connection.pyx", line 682, in _read_query_result
    await result.read()
  File "asyncmy/connection.pyx", line 1069, in read
    first_packet = await self.connection.read_packet()
  File "asyncmy/connection.pyx", line 623, in read_packet
    raise errors.OperationalError(
          │      └ <class 'asyncmy.errors.OperationalError'>
          └ <module 'asyncmy.errors' from '/usr/local/lib/python3.12/site-packages/asyncmy/errors.cpython-312-x86_64-linux-gnu.so'>

asyncmy.errors.OperationalError: (2013, 'Lost connection to MySQL server during query')

Here's the AttributeError Exception


─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /meilisync/meilisync/main.py:155 in start                                                        │
│                                                                                                  │
│   152 │   │   lock = asyncio.Lock()                                                              │
│   153 │   │   await asyncio.gather(_(), interval())                                              │
│   154 │                                                                                          │
│ ❱ 155 │   asyncio.run(run())                                                                     │
│   156                                                                                            │
│   157                                                                                            │
│   158 @app.command(help="Refresh all data by swap index")                                        │
│                                                                                                  │
│ /usr/local/lib/python3.12/asyncio/runners.py:194 in run                                          │
│                                                                                                  │
│   191 │   │   │   "asyncio.run() cannot be called from a running event loop")                    │
│   192 │                                                                                          │
│   193 │   with Runner(debug=debug, loop_factory=loop_factory) as runner:                         │
│ ❱ 194 │   │   return runner.run(main)                                                            │
│   195                                                                                            │
│   196                                                                                            │
│   197 def _cancel_all_tasks(loop):                                                               │
│                                                                                                  │
│                                                                                                  │
│ /usr/local/lib/python3.12/asyncio/runners.py:118 in run                                          │
│                                                                                                  │
│   115 │   │                                                                                      │
│   116 │   │   self._interrupt_count = 0                                                          │
│   117 │   │   try:                                                                               │
│ ❱ 118 │   │   │   return self._loop.run_until_complete(task)                                     │
│   119 │   │   except exceptions.CancelledError:                                                  │
│   120 │   │   │   if self._interrupt_count > 0:                                                  │
│   121 │   │   │   │   uncancel = getattr(task, "uncancel", None)                                 │
│                                                                                                  │
│                                                                                                  │
│ /usr/local/lib/python3.12/asyncio/base_events.py:684 in run_until_complete                       │
│                                                                                                  │
│    681 │   │   if not future.done():                                                             │
│    682 │   │   │   raise RuntimeError('Event loop stopped before Future completed.')             │
│    683 │   │                                                                                     │
│ ❱  684 │   │   return future.result()                                                            │
│    685 │                                                                                         │
│    686 │   def stop(self):                                                                       │
│    687 │   │   """Stop running the event loop.                                                   │
│                                                                                                  │
│                                                                                                  │
│ /meilisync/meilisync/main.py:153 in run                                                          │
│                                                                                                  │
│   150 │   async def run():                                                                       │
│   151 │   │   nonlocal lock                                                                      │
│   152 │   │   lock = asyncio.Lock()                                                              │
│ ❱ 153 │   │   await asyncio.gather(_(), interval())                                              │
│   154 │                                                                                          │
│   155 │   asyncio.run(run())                                                                     │
│   156                                                                                            │
│                                                                                                  │
│                                                                                                  │
│ /meilisync/meilisync/main.py:102 in _                                                            │
│                                                                                                  │
│    99 │   │   │   │   │   │   f'No data found for table "{settings.source.database}.{sync.tabl   │
│   100 │   │   │   │   │   )                                                                      │
│   101 │   │   logger.info(f'Start increment sync data from "{settings.source.type}" to MeiliSe   │
│ ❱ 102 │   │   async for event in source:                                                         │
│   103 │   │   │   if settings.debug:                                                             │
│   104 │   │   │   │   logger.debug(event)                                                        │
│   105 │   │   │   current_progress = event.progress                                              │
│                                                                                                  │
│ /meilisync/meilisync/source/mysql.py:108 in __aiter__                                            │
│                                                                                                  │
│   105 │   │   await self._create_stream()                                                        │
│   106 │   │   while True:                                                                        │
│   107 │   │   │   try:                                                                           │
│ ❱ 108 │   │   │   │   async for event in self.stream:                                            │
│   109 │   │   │   │   │   self.ctl_conn = await asyncmy.connect(**self.kwargs)                   │
│   110 │   │   │   │   │   if isinstance(event, WriteRowsEvent):                                  │
│   111 │   │   │   │   │   │   event_type = EventType.create                                      │
│                                                                                                  │
│ /usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py:374 in __anext__     │
│                                                                                                  │
│   371 │   │   │   await self._connect()                                                          │
│   372 │   │   ret = await self._read()                                                           │
│   373 │   │   while ret is None:                                                                 │
│ ❱ 374 │   │   │   ret = await self._read()                                                       │
│   375 │   │   │   continue                                                                       │
│   376 │   │   return ret                                                                         │
│   377                                                                                            │
│                                                                                                  │
│ /usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py:310 in _read         │
│                                                                                                  │
│   307 │   │   │   self._ignored_schemas,                                                         │
│   308 │   │   │   self._freeze_schema,                                                           │
│   309 │   │   )                                                                                  │
│ ❱ 310 │   │   await binlog_event.init()                                                          │
│   311 │   │                                                                                      │
│   312 │   │   if binlog_event.event_type == ROTATE_EVENT:                                        │
│   313 │   │   │   self._master_log_position = binlog_event.event.position                        │
│                                                                                                  │
│ /usr/local/lib/python3.12/site-packages/asyncmy/replication/packets.py:140 in init               │
│                                                                                                  │
│   137 │   │   │   self.event = None                                                              │
│   138 │                                                                                          │
│   139 │   async def init(self):                                                                  │
│ ❱ 140 │   │   self.event and await self.event.init()                                             │
│   141 │                                                                                          │
│   142 │   def read(self, size):                                                                  │
│   143 │   │   size = int(size)                                                                   │
│                                                                                                  │
│ /usr/local/lib/python3.12/site-packages/asyncmy/replication/row_events.py:580 in init            │
│                                                                                                  │
│   577 │   │   │   self.column_schemas = self._table_map[self.table_id].column_schemas            │
│   578 │   │   else:                                                                              │
│   579 │   │   │   self.column_schemas = await (                                                  │
│ ❱ 580 │   │   │   │   await self._connection._get_table_information(self.schema, self.table_na   │
│   581 │   │   │   )                                                                              │
│   582 │   │   ordinal_pos_loc = 0                                                                │
│   583                                                                                            │
│                                                                                                  │
│ /usr/local/lib/python3.12/site-packages/asyncmy/replication/binlogstream.py:350 in               │
│ _get_table_information                                                                           │
│                                                                                                  │
│   347 │                                                                                          │
│   348 │   async def _get_table_information(self, schema, table):                                 │
│   349 │   │   async with self._ctl_connection.cursor(DictCursor) as cursor:                      │
│ ❱ 350 │   │   │   await cursor.execute(                                                          │
│   351 │   │   │   │   """                                                                        │
│   352 │   │   │   │   │   SELECT                                                                 │
│   353 │   │   │   │   │   │   COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME,                   │
│                                                                                                  │
│                                                                                                  │
│ in execute:179                                                                                   │
│                                                                                                  │
│ in _query:364                                                                                    │
│                                                                                                  │
│ in query:493                                                                                     │
│                                                                                                  │
│ in _execute_command:729                                                                          │
│                                                                                                  │
│ in asyncmy.connection.Connection._write_bytes:668                                                │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'NoneType' object has no attribute 'write'

releasing memory

Is it possible to release the memory insert_interval or every insert_size? I currently have 400GB of data. Initial sync is not possible as of the moment.

ERROR argument after ** must be a mapping, not NoneType

I'm attempting to sync from postgres and I'm consistently getting this error below when running the latest code from this repo. Although I think the sync is still somewhat working because I can see the record count go up in the index and it appears to be matching the offset count of the current running query in postgres.

Looking at where this error is coming from does this mean that some some inserts into Meilisearch are working, but some batches are failing? It's hard to determine if records are missing while the sync is in progress and this database is over 20M records.

The full trace is below.

2024-01-20 20:18:43.045 | ERROR    | meilisync.main:interval:132 - meilisync.progress.file.File.set() argument after ** must be a mapping, not NoneType
Traceback (most recent call last):

  File "/usr/local/bin/meilisync", line 6, in <module>
    sys.exit(app())
    │   │    └ <typer.main.Typer object at 0x7f9815c273e0>
    │   └ <built-in function exit>
    └ <module 'sys' (built-in)>
  File "/usr/local/lib/python3.12/site-packages/typer/main.py", line 311, in __call__
    return get_command(self)(*args, **kwargs)
           │           │      │       └ {}
           │           │      └ ()
           │           └ <typer.main.Typer object at 0x7f9815c273e0>
           └ <function get_command at 0x7f9817b5e340>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           │    │     │       └ {}
           │    │     └ ()
           │    └ <function TyperGroup.main at 0x7f9817b5cc20>
           └ <TyperGroup callback>
  File "/usr/local/lib/python3.12/site-packages/typer/core.py", line 778, in main
    return _main(
           └ <function _main at 0x7f9817b53b00>
  File "/usr/local/lib/python3.12/site-packages/typer/core.py", line 216, in _main
    rv = self.invoke(ctx)
         │    │      └ <click.core.Context object at 0x7f9815184d40>
         │    └ <function MultiCommand.invoke at 0x7f9817f84720>
         └ <TyperGroup callback>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1688, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
           │               │       │       │      └ <click.core.Context object at 0x7f981516e7e0>
           │               │       │       └ <function Command.invoke at 0x7f9817f840e0>
           │               │       └ <TyperCommand start>
           │               └ <click.core.Context object at 0x7f981516e7e0>
           └ <function MultiCommand.invoke.<locals>._process_result at 0x7f981455bce0>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           │   │      │    │           │   └ {}
           │   │      │    │           └ <click.core.Context object at 0x7f981516e7e0>
           │   │      │    └ <function start at 0x7f9814559bc0>
           │   │      └ <TyperCommand start>
           │   └ <function Context.invoke at 0x7f9817f62a20>
           └ <click.core.Context object at 0x7f981516e7e0>
  File "/usr/local/lib/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
                       │       └ {}
                       └ ()
  File "/usr/local/lib/python3.12/site-packages/typer/main.py", line 683, in wrapper
    return callback(**use_params)  # type: ignore
           │          └ {'context': <click.core.Context object at 0x7f981516e7e0>}
           └ <function start at 0x7f98145599e0>

  File "/meilisync/meilisync/main.py", line 140, in start
    asyncio.run(run())
    │       │   └ <function start.<locals>.run at 0x7f98145354e0>
    │       └ <function run at 0x7f9818b0de40>
    └ <module 'asyncio' from '/usr/local/lib/python3.12/asyncio/__init__.py'>

  File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           │      │   └ <coroutine object start.<locals>.run at 0x7f981452aa40>
           │      └ <function Runner.run at 0x7f98180bfc40>
           └ <asyncio.runners.Runner object at 0x7f9815df0440>
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           │    │     │                  └ <Task pending name='Task-4' coro=<start.<locals>.run() running at /meilisync/meilisync/main.py:138> wait_for=<_GatheringFutur...
           │    │     └ <function BaseEventLoop.run_until_complete at 0x7f98180bd8a0>
           │    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
           └ <asyncio.runners.Runner object at 0x7f9815df0440>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 671, in run_until_complete
    self.run_forever()
    │    └ <function BaseEventLoop.run_forever at 0x7f98180bd800>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 638, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x7f98180bf600>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 1971, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x7f9818219a80>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>
  File "/usr/local/lib/python3.12/asyncio/events.py", line 84, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>

> File "/meilisync/meilisync/main.py", line 130, in interval
    await progress.set(**current_progress)
          │        │     └ None
          │        └ <function File.set at 0x7f98151249a0>
          └ <meilisync.progress.file.File object at 0x7f981490ccb0>

TypeError: meilisync.progress.file.File.set() argument after ** must be a mapping, not NoneType
2024-01-20 20:18:43.053 | ERROR    | meilisync.main:interval:133 - Error when insert data to MeiliSearch: meilisync.progress.file.File.set() argument after ** must be a mapping, not NoneTyp

Delete Operation in PostgreSQL is not Synced

(Apologies for the second issue in such a short time).

The delete operation doesn't work as expected; it doesn't sync with MeiliSearch. I'm using the debezium/postgres Docker image, which comes with the wal_level configured and wal2json installed.
I'm not seeing any logs or errors when performing delete operations.

On the wal2json GitHub page, it mentions:

wal2json is an output plugin for logical decoding. It means that the plugin has access to tuples produced by INSERT and UPDATE. Also, UPDATE/DELETE old row versions can be accessed depending on the configured replica identity.

I'm unsure about what they mean by "old row versions" and how to configure it to work with meilisync, if it's even possible.

By the way debzeium/postgres versions 12 and 14 have no other problems, but with version 15, meilisync throws an error mentioning something like "can't locate pg_wal," if I remember correctly.

Improve how to contribute sections

Currently, if someone wants to contribute is quite challenging.

  • Add the tests to the CI configuration (today only the linter is required)
  • Configure a more straightforward way to try the code without building it in every change. (At least I had to do it every time).
  • Provide a docker-compose.yml for development (with MongoDB, MySQL, and PostgreSQL)
  • Introduce a CONTRIBUTING.md

MariaDB support

Do you support MariaDB? If not, is there plans to add support for it? I prefer to use MariaDB fork over MySQL,and I guess a lot of people too. Thanks.

TypeError: 'async for' requires an object with __aiter__ method, got list

meilisync-1 | │ /meilisync/meilisync/meili.py:33 in add_full_data │
meilisync-1 | │ │
meilisync-1 | │ 30 │ async def add_full_data(self, sync: Sync, data: AsyncGenerator): │
meilisync-1 | │ 31 │ │ tasks = [] │
meilisync-1 | │ 32 │ │ count = 0 │
meilisync-1 | │ ❱ 33 │ │ async for items in data: │
meilisync-1 | │ 34 │ │ │ count += len(items) │
meilisync-1 | │ 35 │ │ │ events = [Event(type=EventType.create, data=item) for item │
meilisync-1 | │ 36 │ │ │ task = await self.handle_events_by_type(sync, events, Even │
meilisync-1 | │ │
meilisync-1 | │ ╭───────────────────────────────── locals ─────────────────────────────────╮ │
meilisync-1 | │ │ count = 0 │ │
meilisync-1 | │ │ data = [ │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 1, │ │
meilisync-1 | │ │ │ │ 'name': '王者天下 第五季', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 15), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 2, │ │
meilisync-1 | │ │ │ │ 'name': '少女与战车 最终章', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 15), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 3, │ │
meilisync-1 | │ │ │ │ 'name': │ │
meilisync-1 | │ │ '被称为废物的原英雄,被家里流放后随心所欲地活下去', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 16), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 4, │ │
meilisync-1 | │ │ │ │ 'name': '单间、光照尚好、附带天使。', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 16), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 5, │ │
meilisync-1 | │ │ │ │ 'name': '关于我转生变成史莱姆这档事 第三季', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 16), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 6, │ │
meilisync-1 | │ │ │ │ 'name': 'Wonderful 光之美少女!', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 16), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 7, │ │
meilisync-1 | │ │ │ │ 'name': '我心里危险的东西 第二季', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 17), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 9, │ │
meilisync-1 | │ │ │ │ 'name': '假面骑士歌查德', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 17), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 10, │ │
meilisync-1 | │ │ │ │ 'name': '宝可梦 地平线 莉可与罗伊踏上旅途', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 17), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ }, │ │
meilisync-1 | │ │ │ { │ │
meilisync-1 | │ │ │ │ 'anime_id': 11, │ │
meilisync-1 | │ │ │ │ 'name': '香格里拉·弗陇提亚~屎作猎人向神作发起挑战~', │ │
meilisync-1 | │ │ │ │ 'play_count': 0, │ │
meilisync-1 | │ │ │ │ 'danmaku_count': 0, │ │
meilisync-1 | │ │ │ │ 'like_count': 0, │ │
meilisync-1 | │ │ │ │ 'create_by': 'admin', │ │
meilisync-1 | │ │ │ │ 'update_by': None, │ │
meilisync-1 | │ │ │ │ 'create_time': datetime.datetime(2024, 4, 1, 18, 22, │ │
meilisync-1 | │ │ 17), │ │
meilisync-1 | │ │ │ │ 'update_time': None, │ │
meilisync-1 | │ │ │ │ 'is_deleted': 0, │ │
meilisync-1 | │ │ │ │ ... +7 │ │
meilisync-1 | │ │ │ } │ │
meilisync-1 | │ │ ] │ │
meilisync-1 | │ │ self = <meilisync.meili.Meili object at 0x7f9f00d63e60> │ │
meilisync-1 | │ │ sync = Sync( │ │
meilisync-1 | │ │ │ plugins=['meilisync.plugin.Plugin'], │ │
meilisync-1 | │ │ │ table='dmkh_animes', │ │
meilisync-1 | │ │ │ pk='anime_id', │ │
meilisync-1 | │ │ │ full=True, │ │
meilisync-1 | │ │ │ index='animes', │ │
meilisync-1 | │ │ │ fields=None │ │
meilisync-1 | │ │ ) │ │
meilisync-1 | │ │ tasks = [] │ │
meilisync-1 | │ ╰──────────────────────────────────────────────────────────────────────────╯ │
meilisync-1 | ╰──────────────────────────────────────────────────────────────────────────────╯
meilisync-1 | TypeError: 'async for' requires an object with aiter method, got list
meilisync-1 exited with code 1

No matching distribution found for meilisync

Hey!

Was trying to use your tool, but pip command doesn't really work for me.

Collecting meilisync
  Could not find a version that satisfies the requirement meilisync (from versions: )
No matching distribution found for meilisync
python3 -V
Python 3.6.9
python -V
Python 2.7.17

Was trying to install pip for both of them and used on both of them. Other packages do install though.

Primary key field documentation

Hello,

I've noticed that the documentation doesn't explain how to configure a table's primary key.

AFAIK this is achieved by adding the pk field in the YAML configuration. But there might be some additional information we want to add that I'm not aware of.

Cheers,

Support for Typesense sync target

The code-base seems to have quite good abstractions for making it possible to swap out Meilisearch from the target sync system for other search-tailored document databases. I came across this project while looking for Postgresql -> Typesense or Postgresql -> Meilisearch syncing tools but would prefer to use typesense as my search database due to its vector search capability. Currently this syncing is achievable by using Airbyte but that's way too big overkill and hard to adopt compared to this project.

Unable to insert date fields from mysql source

Hi @long2ice, first of all, thanks a lot for this tool. It will be incredibly helpful for everyone. Congratulations on your work!

Before officially starting sharing this tool, I want to ensure everything is working fine, so I'll raise some issues where we can work together to improve it!

So, my table is quite simple:

mysql> SHOW FULL FIELDS FROM authors;
+------------+--------------+--------------------+------+-----+-------------------+-------------------+---------------------------------+---------+
| Field      | Type         | Collation          | Null | Key | Default           | Extra             | Privileges                      | Comment |
+------------+--------------+--------------------+------+-----+-------------------+-------------------+---------------------------------+---------+
| id         | int          | NULL               | NO   | PRI | NULL              | auto_increment    | select,insert,update,references |         |
| first_name | varchar(50)  | utf8mb3_unicode_ci | NO   |     | NULL              |                   | select,insert,update,references |         |
| last_name  | varchar(50)  | utf8mb3_unicode_ci | NO   |     | NULL              |                   | select,insert,update,references |         |
| email      | varchar(100) | utf8mb3_unicode_ci | NO   | UNI | NULL              |                   | select,insert,update,references |         |
| birthdate  | date         | NULL               | NO   |     | NULL              |                   | select,insert,update,references |         |
| added      | timestamp    | NULL               | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED | select,insert,update,references |         |
+------------+--------------+--------------------+------+-----+-------------------+-------------------+---------------------------------+---------+

fake data example:

INSERT INTO `authors` (`id`, `first_name`, `last_name`, `email`, `birthdate`, `added`) VALUES (1, 'Flo', 'Hansen', '[email protected]', '2012-06-07', '1977-01-27 22:00:53');
meilisync-meilisync-1    | TypeError: Object of type date is not JSON serializable
meilisync-meilisync-1    | 2023-04-21 15:32:23.624 | ERROR    | meilisync.main:interval:122 - Error when insert data to MeiliSearch: Object of type date is not JSON serializable

I'm running this MySQL version: 8.0.33 for Linux on aarch64 (MySQL Community Server - GPL).
And the import was not completed. Since there is no way to inform in the configuration file which types the column has, the system should automatically understand it, right?

Meilisync Not Resyncing After Deleting Documents

Hello!

I'm having trouble with Meilisync after deleting all documents in Meilisearch. It seems like Meilisync isn't picking up the changes and resyncing the data.

Is there a way to manually trigger a resync in Meilisync after deleting documents in Meilisearch, or is there a specific configuration needed to handle this scenario?

Mongo ObjectId fields issue

While _id is not only ObjectId field in most collection, any other objectId may cause below error:

│                                                                              │
│ /usr/local/lib/python3.12/json/encoder.py:180 in default                     │
│                                                                              │
│   177 │   │   │   │   return JSONEncoder.default(self, o)                    │
│   178 │   │                                                                  │
│   179 │   │   """                                                            │
│ ❱ 180 │   │   raise TypeError(f'Object of type {o.__class__.__name__} '      │
│   181 │   │   │   │   │   │   f'is not JSON serializable')                   │
│   182 │                                                                      │
│   183 │   def encode(self, o):                                               │
│                                                                              │
│ ╭────────────────────────── locals ──────────────────────────╮               │
│ │    o = ObjectId('637e1e913e9e101f8f9150b5')                │               │
│ │ self = <json.encoder.JSONEncoder object at 0x7ab18389f770> │               │
│ ╰────────────────────────────────────────────────────────────╯               │
╰──────────────────────────────────────────────────────────────────────────────╯
TypeError: Object of type ObjectId is not JSON serializable

So, It would be better if we can define stringify some fields right in config.yml, example like this:

  - table: picture
    index: beauty-pictures
    full: true
    fields:
      id: str(id)
      uid: str(id)
      description: description
      category: category

asyncmy.errors.OperationalError: (1236, "A replica with the same server_uuid/server_id as this replica has connected to the source

run meilisync start on Docker (with Laravel)

docker error logs (mysql:8-debian)

backend-meilisync-1     | 2023-10-26 00:23:04.096 | ERROR    | meilisync.source.mysql:__aiter__:121 - Binlog stream error: (1236, "A replica with the same server_uuid/server_id as this replica has connected to the source; the first event 'binlog.000028' at 157, the last event read from './binlog.000028' at 126, the last byte read from './binlog.000028' at 157."), restart...
backend-meilisync-1     | Traceback (most recent call last):
backend-meilisync-1     |
backend-meilisync-1     |   File "/usr/local/bin/meilisync", line 6, in <module>
backend-meilisync-1     |     sys.exit(app())
backend-meilisync-1     |     │   │    └ <typer.main.Typer object at 0x4001f89790>
backend-meilisync-1     |     │   └ <built-in function exit>
backend-meilisync-1     |     └ <module 'sys' (built-in)>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/typer/main.py", line 311, in __call__
backend-meilisync-1     |     return get_command(self)(*args, **kwargs)
backend-meilisync-1     |            │           │      │       └ {}
backend-meilisync-1     |            │           │      └ ()
backend-meilisync-1     |            │           └ <typer.main.Typer object at 0x4001f89790>
backend-meilisync-1     |            └ <function get_command at 0x400347e700>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/click/core.py", line 1157, in __call__
backend-meilisync-1     |     return self.main(*args, **kwargs)
backend-meilisync-1     |            │    │     │       └ {}
backend-meilisync-1     |            │    │     └ ()
backend-meilisync-1     |            │    └ <function TyperGroup.main at 0x4003455040>
backend-meilisync-1     |            └ <TyperGroup callback>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/typer/core.py", line 778, in main
backend-meilisync-1     |     return _main(
backend-meilisync-1     |            └ <function _main at 0x40033a9310>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/typer/core.py", line 216, in _main
backend-meilisync-1     |     rv = self.invoke(ctx)
backend-meilisync-1     |          │    │      └ <click.core.Context object at 0x4007d5e100>
backend-meilisync-1     |          │    └ <function MultiCommand.invoke at 0x4002c50550>
backend-meilisync-1     |          └ <TyperGroup callback>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/click/core.py", line 1688, in invoke
backend-meilisync-1     |     return _process_result(sub_ctx.command.invoke(sub_ctx))
backend-meilisync-1     |            │               │       │       │      └ <click.core.Context object at 0x40052ed250>
backend-meilisync-1     |            │               │       │       └ <function Command.invoke at 0x4002c50040>
backend-meilisync-1     |            │               │       └ <TyperCommand start>
backend-meilisync-1     |            │               └ <click.core.Context object at 0x40052ed250>
backend-meilisync-1     |            └ <function MultiCommand.invoke.<locals>._process_result at 0x40083c2820>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/click/core.py", line 1434, in invoke
backend-meilisync-1     |     return ctx.invoke(self.callback, **ctx.params)
backend-meilisync-1     |            │   │      │    │           │   └ {}
backend-meilisync-1     |            │   │      │    │           └ <click.core.Context object at 0x40052ed250>
backend-meilisync-1     |            │   │      │    └ <function start at 0x4008472a60>
backend-meilisync-1     |            │   │      └ <TyperCommand start>
backend-meilisync-1     |            │   └ <function Context.invoke at 0x4002c4adc0>
backend-meilisync-1     |            └ <click.core.Context object at 0x40052ed250>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/click/core.py", line 783, in invoke
backend-meilisync-1     |     return __callback(*args, **kwargs)
backend-meilisync-1     |                        │       └ {}
backend-meilisync-1     |                        └ ()
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/typer/main.py", line 683, in wrapper
backend-meilisync-1     |     return callback(**use_params)  # type: ignore
backend-meilisync-1     |            │          └ {'context': <click.core.Context object at 0x40052ed250>}
backend-meilisync-1     |            └ <function start at 0x4008472700>
backend-meilisync-1     |
backend-meilisync-1     |   File "/meilisync/meilisync/main.py", line 140, in start
backend-meilisync-1     |     asyncio.run(run())
backend-meilisync-1     |     │       │   └ <function start.<locals>.run at 0x40083c2940>
backend-meilisync-1     |     │       └ <function run at 0x40029d6e50>
backend-meilisync-1     |     └ <module 'asyncio' from '/usr/local/lib/python3.9/asyncio/__init__.py'>
backend-meilisync-1     |
backend-meilisync-1     |   File "/usr/local/lib/python3.9/asyncio/runners.py", line 44, in run
backend-meilisync-1     |     return loop.run_until_complete(main)
backend-meilisync-1     |            │    │                  └ <coroutine object start.<locals>.run at 0x400845b6c0>
backend-meilisync-1     |            │    └ <function BaseEventLoop.run_until_complete at 0x40029ec8b0>
backend-meilisync-1     |            └ <_UnixSelectorEventLoop running=True closed=False debug=False>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/asyncio/base_events.py", line 634, in run_until_complete
backend-meilisync-1     |     self.run_forever()
backend-meilisync-1     |     │    └ <function BaseEventLoop.run_forever at 0x40029ec820>
backend-meilisync-1     |     └ <_UnixSelectorEventLoop running=True closed=False debug=False>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/asyncio/base_events.py", line 601, in run_forever
backend-meilisync-1     |     self._run_once()
backend-meilisync-1     |     │    └ <function BaseEventLoop._run_once at 0x40029f23a0>
backend-meilisync-1     |     └ <_UnixSelectorEventLoop running=True closed=False debug=False>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/asyncio/base_events.py", line 1905, in _run_once
backend-meilisync-1     |     handle._run()
backend-meilisync-1     |     │      └ <function Handle._run at 0x400240adc0>
backend-meilisync-1     |     └ <Handle <TaskWakeupMethWrapper object at 0x4007e71dc0>(<Future finished result=None>)>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/asyncio/events.py", line 80, in _run
backend-meilisync-1     |     self._context.run(self._callback, *self._args)
backend-meilisync-1     |     │    │            │    │           │    └ <member '_args' of 'Handle' objects>
backend-meilisync-1     |     │    │            │    │           └ <Handle <TaskWakeupMethWrapper object at 0x4007e71dc0>(<Future finished result=None>)>
backend-meilisync-1     |     │    │            │    └ <member '_callback' of 'Handle' objects>
backend-meilisync-1     |     │    │            └ <Handle <TaskWakeupMethWrapper object at 0x4007e71dc0>(<Future finished result=None>)>
backend-meilisync-1     |     │    └ <member '_context' of 'Handle' objects>
backend-meilisync-1     |     └ <Handle <TaskWakeupMethWrapper object at 0x4007e71dc0>(<Future finished result=None>)>
backend-meilisync-1     |
backend-meilisync-1     |   File "/meilisync/meilisync/main.py", line 102, in _
backend-meilisync-1     |     async for event in source:
backend-meilisync-1     |               │        └ <meilisync.source.mysql.MySQL object at 0x4004fd0880>
backend-meilisync-1     |               └ ProgressEvent(progress={'master_log_file': 'binlog.000028', 'master_log_position': 157})
backend-meilisync-1     |
backend-meilisync-1     | > File "/meilisync/meilisync/source/mysql.py", line 100, in __aiter__
backend-meilisync-1     |     async for event in self.stream:
backend-meilisync-1     |                        │    └ <asyncmy.replication.binlogstream.BinLogStream object at 0x4007f52d60>
backend-meilisync-1     |                        └ <meilisync.source.mysql.MySQL object at 0x4004fd0880>
backend-meilisync-1     |
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/asyncmy/replication/binlogstream.py", line 373, in __anext__
backend-meilisync-1     |     ret = await self._read()
backend-meilisync-1     |                 │    └ <function BinLogStream._read at 0x400558eee0>
backend-meilisync-1     |                 └ <asyncmy.replication.binlogstream.BinLogStream object at 0x4007f52d60>
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/asyncmy/replication/binlogstream.py", line 288, in _read
backend-meilisync-1     |     raise e
backend-meilisync-1     |   File "/usr/local/lib/python3.9/site-packages/asyncmy/replication/binlogstream.py", line 282, in _read
backend-meilisync-1     |     pkt = await self._connection.read_packet()
backend-meilisync-1     |                 │    │           └ <cyfunction Connection.read_packet at 0x40054235f0>
backend-meilisync-1     |                 │    └ <asyncmy.connection.Connection object at 0x4007d932b0>
backend-meilisync-1     |                 └ <asyncmy.replication.binlogstream.BinLogStream object at 0x4007f52d60>
backend-meilisync-1     |   File "asyncmy/connection.pyx", line 644, in read_packet
backend-meilisync-1     |     packet.raise_for_error()
backend-meilisync-1     |   File "asyncmy/protocol.pyx", line 190, in asyncmy.protocol.MysqlPacket.raise_for_error
backend-meilisync-1     |     cpdef raise_for_error(self):
backend-meilisync-1     |   File "asyncmy/protocol.pyx", line 194, in asyncmy.protocol.MysqlPacket.raise_for_error
backend-meilisync-1     |     errors.raise_mysql_exception(self._data)
backend-meilisync-1     |     │      └ <cyfunction raise_mysql_exception at 0x40053102b0>
backend-meilisync-1     |     └ <module 'asyncmy.errors' from '/usr/local/lib/python3.9/site-packages/asyncmy/errors.cpython-39-x86_64-linux-gnu.so'>
backend-meilisync-1     |   File "asyncmy/errors.pyx", line 128, in asyncmy.errors.raise_mysql_exception
backend-meilisync-1     |     cpdef raise_mysql_exception(bytes data):
backend-meilisync-1     |           └ <cyfunction raise_mysql_exception at 0x40053102b0>
backend-meilisync-1     |   File "asyncmy/errors.pyx", line 137, in asyncmy.errors.raise_mysql_exception
backend-meilisync-1     |     raise error_class(errno, err_val)
backend-meilisync-1     |
backend-meilisync-1     | asyncmy.errors.OperationalError: (1236, "A replica with the same server_uuid/server_id as this replica has connected to the source; the first event 'binlog.000028' at 157, the last event read from './binlog.000028' at 126, the last byte read from './binlog.000028' at 157.")

(docker up log)

backend-meilisync-1     | 2023-10-26 01:14:26.378 | DEBUG    | meilisync.main:_:36 - plugins=[] progress=Progress(type=<ProgressType.file: 'file'>) debug=True source=Source(type=<SourceType.mysql: 'mysql'>, database='example_app', host='mysql', port=3306, user='mai', password='password') meilisearch=MeiliSearch(api_url='http://meilisearch:7700', api_key='cb35...', insert_size=100, insert_interval=10) sync=[Sync(plugins=[], table='animals', pk='id', full=False, index='catsearch_settings', fields={'id': None, 'travel_type': None, 'category_group': None, 'title': None, 'created_at': None, 'updated_at': None})] sentry=Sentry(dsn='', environment='production')
backend-meilisync-1     | 2023-10-26 01:14:27.155 | INFO     | meilisync.main:_:101 - Start increment sync data from "mysql" to MeiliSearch...
backend-meilisync-1     | 2023-10-26 01:14:27.413 | DEBUG    | meilisync.main:_:104 - progress={'master_log_file': 'binlog.000002', 'master_log_position': 157}

meilisync/config.yml

debug: true
progress:
  type: file
source:
  type: mysql
  host: mysql
  port: 3306
  user: dbuser
  password: password
  database: example_app
meilisearch:
  api_url: http://meilisearch:7700
  api_key: <MY_ADMIN_API_KEY>
  insert_size: 100
  insert_interval: 10
sync:
  - table: animals
    index: animals
    full: false
    fields:
      id:
      title:
      created_at:
      updated_at:

my.cnf

[mysqld]
character-set-server    = utf8mb4
collation-server        = utf8mb4_unicode_ci
explicit-defaults-for-timestamp = 1
secure-file-priv        = ""
max_allowed_packet      = 32M
max_connections         = 500
max_user_connections    = 10

[client]
default-character-set   = utf8mb4

binlog_format = ROW

mysql> SHOW GLOBAL VARIABLES LIKE 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
1 row in set (0.04 sec)

show grants;

GRANT APPLICATION_PASSWORD_ADMIN,AUDIT_ABORT_EXEMPT,AUDIT_ADMIN,AUTHENTICATION_POLICY_ADMIN,BACKUP_ADMIN,BINLOG_ADMIN,BINLOG_ENCRYPTION_ADMIN,CLONE_ADMIN,CONNECTION_ADMIN,ENCRYPTION_KEY_ADMIN,FIREWALL_EXEMPT,FLUSH_OPTIMIZER_COSTS,FLUSH_STATUS,FLUSH_TABLES,FLUSH_USER_RESOURCES,GROUP_REPLICATION_ADMIN,GROUP_REPLICATION_STREAM,INNODB_REDO_LOG_ARCHIVE,INNODB_REDO_LOG_ENABLE,PASSWORDLESS_USER_ADMIN,PERSIST_RO_VARIABLES_ADMIN,REPLICATION_APPLIER,REPLICATION_SLAVE_ADMIN,RESOURCE_GROUP_ADMIN,RESOURCE_GROUP_USER,ROLE_ADMIN,SENSITIVE_VARIABLES_OBSERVER,SERVICE_CONNECTION_ADMIN,SESSION_VARIABLES_ADMIN,SET_USER_ID,SHOW_ROUTINE,SYSTEM_USER,SYSTEM_VARIABLES_ADMIN,TABLE_ENCRYPTION_ADMIN,TELEMETRY_LOG_ADMIN,XA_RECOVER_ADMIN ON *.* TO `mai`@`localhost` WITH GRANT OPTION

Related Field Referencing in Meilisync?

I couldn't find anything in the documentation mentioning this feature, but it would be nice if meilisync could reference related fields (foreign keys in MySQL and PostgreSQL)

Here's an example of how it could work:

    fields:
      id:
      title:
      user__username: username

or

    fields:
      id:
      title:
      user: 
        username:

Edit:
Now that I think about it; the tricky part would be dealing with updates to related fields.

Postgres timestamp column causes sync to fail

Postgres: v15

config.yml

progress: 
  type: file 

I am trying to sync a table from a Postgres database with the column type:timestamp, and its throwing this error.
image

This is an example of a row in the database
image

Seems like the error occurs when its trying to do a json.dumps into the progress.json file.

Edit: Tried with redis but still getting the same error, issue might not lie with writing to progress

Docker release

HI, can you release current meilisync state on docker hub?

Module instrospection seems to break EXE build with pyinstaller

I successfully built a one file executable on Windows using Python 3.12 an pyinstaller, however I am getting an error that the value "file" is not valid for progress type. I think the same error happens for source type "mongo" as well.

Is it possible that the way these classes are found via introspection here is too complex for pyinstaller?

Would be great to be able to package both the Meilisearch executable and your great sync tool along with a C++ based end user app that uses MongoDB :-D

def _discover(module: ModuleType, t: Type):
    ret = {}
    for m in pkgutil.iter_modules(module.__path__):
        mod = importlib.import_module(f"{module.__name__}.{m.name}")
        for _, member in inspect.getmembers(mod, inspect.isclass):
            if issubclass(member, t) and member is not t:
                ret[member.type] = member
    return ret

wait_for_task timeout

@long2ice I saw in c9ab8a6 you extended the default timeout for wait_for_task. I had a request from a user to make it possible to wait indefinitely that I implemented. This is possible in meilisearch-python-async >= 1.2.0 by passing None to the timeout. The update you made is no problem, I just wanted to let you know the None option is now available if you want to use it.

Need some clarification

Trying to understand this and if it is what I need.

Does it just sync whole tables flat? Can I use a view? Can it take a query?

This is a partial piece of the documents I need to sync.
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.