mirror of
https://github.com/boostorg/redis.git
synced 2026-01-27 07:12:08 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
394bdf5b5e | ||
|
|
a4745a1a5d | ||
|
|
b99da962d1 | ||
|
|
172de6235c | ||
|
|
5e99a58685 | ||
|
|
b952a2d2d8 | ||
|
|
16d1f8df24 | ||
|
|
82b804e397 | ||
|
|
22530724c6 | ||
|
|
610aef5c5e | ||
|
|
87a03a0b6b | ||
|
|
0e3de3688e | ||
|
|
980c39084f | ||
|
|
abd339d0f2 | ||
|
|
a86a18c969 | ||
|
|
f0deda42a4 | ||
|
|
d1e7ec6f03 | ||
|
|
0e8e31f310 | ||
|
|
ba86c9fb05 | ||
|
|
ccef30c8ac | ||
|
|
ce77524d6f | ||
|
|
54a9fddc40 | ||
|
|
ad3127d1ca | ||
|
|
0363353154 | ||
|
|
b453fc63c9 | ||
|
|
1a47dece35 | ||
|
|
048a711e51 | ||
|
|
a940f7f4bf | ||
|
|
fd0cae92ee | ||
|
|
a5376bc05f | ||
|
|
98580eb0ea | ||
|
|
6ed2d96b07 | ||
|
|
245fdb55b6 | ||
|
|
e6443cbe26 | ||
|
|
2101def89f | ||
|
|
ddfe9defc5 | ||
|
|
cd28ff285f | ||
|
|
c4bd338e79 | ||
|
|
bb28f34ecc | ||
|
|
aa6251b96c | ||
|
|
b12140ae8a | ||
|
|
d845434869 | ||
|
|
5e86fb9d03 | ||
|
|
dc80c04347 | ||
|
|
20d6c67f3d | ||
|
|
8c4816fc8d | ||
|
|
18aa9ff726 | ||
|
|
b68e413351 | ||
|
|
27b3bb89fb | ||
|
|
70cd9b0ffd | ||
|
|
43a08e834c |
2
INSTALL
2
INSTALL
@@ -1 +1 @@
|
||||
See file:///tmp/aedis/html/installation.html
|
||||
See https://mzimbres.github.io/aedis/#using-aedis
|
||||
|
||||
373
LICENSE
373
LICENSE
@@ -1,373 +0,0 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
23
LICENSE.txt
Normal file
23
LICENSE.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
54
Makefile.am
54
Makefile.am
@@ -12,25 +12,24 @@ AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += low_level_sync_intro
|
||||
check_PROGRAMS += high_level_intro
|
||||
check_PROGRAMS += high_level_aggregates
|
||||
check_PROGRAMS += high_level_stl_containers
|
||||
check_PROGRAMS += high_level_serialization
|
||||
check_PROGRAMS += intro_sync
|
||||
check_PROGRAMS += serialization_sync
|
||||
check_PROGRAMS += intro_high_level
|
||||
check_PROGRAMS += aggregates_high_level
|
||||
check_PROGRAMS += test_low_level
|
||||
check_PROGRAMS += test_high_level
|
||||
if HAVE_CXX20
|
||||
check_PROGRAMS += low_level_async_intro
|
||||
check_PROGRAMS += low_level_adapter
|
||||
check_PROGRAMS += test_online
|
||||
check_PROGRAMS += transaction
|
||||
check_PROGRAMS += custom_adapter
|
||||
endif
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += high_level_subscriber
|
||||
EXTRA_PROGRAMS += subscriber_high_level
|
||||
EXTRA_PROGRAMS += commands
|
||||
if HAVE_CXX20
|
||||
EXTRA_PROGRAMS += low_level_subscriber
|
||||
EXTRA_PROGRAMS += high_level_echo_server
|
||||
EXTRA_PROGRAMS += high_level_chat_room
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
endif
|
||||
|
||||
CLEANFILES =
|
||||
@@ -39,21 +38,20 @@ CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
high_level_intro_SOURCES = $(top_srcdir)/examples/high_level/intro.cpp
|
||||
high_level_aggregates_SOURCES = $(top_srcdir)/examples/high_level/aggregates.cpp
|
||||
high_level_stl_containers_SOURCES = $(top_srcdir)/examples/high_level/stl_containers.cpp
|
||||
high_level_serialization_SOURCES = $(top_srcdir)/examples/high_level/serialization.cpp
|
||||
low_level_sync_intro_SOURCES = $(top_srcdir)/examples/low_level/sync_intro.cpp
|
||||
intro_high_level_SOURCES = $(top_srcdir)/examples/intro_high_level.cpp
|
||||
aggregates_high_level_SOURCES = $(top_srcdir)/examples/aggregates_high_level.cpp
|
||||
intro_sync_SOURCES = $(top_srcdir)/examples/intro_sync.cpp
|
||||
serialization_sync_SOURCES = $(top_srcdir)/examples/serialization_sync.cpp
|
||||
commands_SOURCES = $(top_srcdir)/tools/commands.cpp
|
||||
high_level_subscriber_SOURCES = $(top_srcdir)/examples/high_level/subscriber.cpp
|
||||
subscriber_high_level_SOURCES = $(top_srcdir)/examples/subscriber_high_level.cpp
|
||||
test_low_level_SOURCES = $(top_srcdir)/tests/low_level.cpp
|
||||
test_high_level_SOURCES = $(top_srcdir)/tests/high_level.cpp
|
||||
if HAVE_CXX20
|
||||
test_online_SOURCES = $(top_srcdir)/tests/online.cpp
|
||||
low_level_async_intro_SOURCES = $(top_srcdir)/examples/low_level/async_intro.cpp
|
||||
low_level_subscriber_SOURCES = $(top_srcdir)/examples/low_level/subscriber.cpp
|
||||
low_level_adapter_SOURCES = $(top_srcdir)/examples/low_level/adapter.cpp
|
||||
high_level_echo_server_SOURCES = $(top_srcdir)/examples/high_level/echo_server.cpp
|
||||
high_level_chat_room_SOURCES = $(top_srcdir)/examples/high_level/chat_room.cpp
|
||||
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
|
||||
subscriber_SOURCES = $(top_srcdir)/examples/subscriber.cpp
|
||||
custom_adapter_SOURCES = $(top_srcdir)/examples/custom_adapter.cpp
|
||||
echo_server_SOURCES = $(top_srcdir)/examples/echo_server.cpp
|
||||
chat_room_SOURCES = $(top_srcdir)/examples/chat_room.cpp
|
||||
endif
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
@@ -61,6 +59,8 @@ nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/redis/command.hpp\
|
||||
$(top_srcdir)/aedis/generic/client.hpp\
|
||||
$(top_srcdir)/aedis/generic/serializer.hpp\
|
||||
$(top_srcdir)/aedis/generic/error.hpp\
|
||||
$(top_srcdir)/aedis/generic/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/generic/detail/client_ops.hpp\
|
||||
$(top_srcdir)/aedis/sentinel/command.hpp\
|
||||
$(top_srcdir)/aedis/aedis.hpp\
|
||||
@@ -68,7 +68,7 @@ nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/adapter/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/adapter/adapt.hpp\
|
||||
$(top_srcdir)/aedis/adapter/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/adapter/detail/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/compose.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/read_ops.hpp\
|
||||
@@ -83,7 +83,9 @@ nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/high_level/user_session.hpp\
|
||||
$(top_srcdir)/examples/user_session.hpp\
|
||||
$(top_srcdir)/examples/print.hpp\
|
||||
$(top_srcdir)/examples/mystruct.hpp\
|
||||
$(top_srcdir)/tests/check.hpp
|
||||
|
||||
TESTS = $(check_PROGRAMS)
|
||||
|
||||
@@ -1,38 +1,50 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_ADAPTER_ADAPT_HPP
|
||||
#define AEDIS_ADAPTER_ADAPT_HPP
|
||||
|
||||
#include <aedis/adapter/response_traits.hpp>
|
||||
#include <aedis/adapter/detail/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
template <class T>
|
||||
using adapter_t = typename detail::adapter_t<T>;
|
||||
|
||||
/** \brief Creates a dummy response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses on which the user is
|
||||
not insterested in.
|
||||
The adapter returned by this function ignores responses. It is
|
||||
useful to avoid wasting time with responses which are not needed.
|
||||
|
||||
Example usage:
|
||||
Example:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
// Pushes and writes some commands to the server.
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
|
||||
// Ignores all responses except for the response to ping.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt()); // hello
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(resp)); // ping
|
||||
resp3::read(socket, dynamic_buffer(buffer, adapt())); // quit
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
{ return detail::response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* All STL containers, \c std::tuple and built-in types are supported and
|
||||
* STL containers, \c std::tuple and built-in types are supported and
|
||||
* can be used in conjunction with \c boost::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
@@ -62,7 +74,9 @@ auto adapt() noexcept
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
{ return detail::response_traits<T>::adapt(t); }
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPT_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
#define AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
@@ -18,7 +18,10 @@
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
@@ -31,6 +34,20 @@ namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
double
|
||||
parse_double(
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
static constexpr boost::spirit::x3::real_parser<double> p{};
|
||||
double ret;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_double;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Serialization.
|
||||
|
||||
template <class T>
|
||||
@@ -51,6 +68,14 @@ void from_string(
|
||||
t = *sv.data() == 't';
|
||||
}
|
||||
|
||||
void from_string(
|
||||
double& d,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
d = parse_double(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
@@ -148,14 +173,14 @@ public:
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
ec = error::expects_set_aggregate;
|
||||
ec = error::expects_set_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_set_aggregate;
|
||||
ec = adapter::error::expects_set_type;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,14 +212,14 @@ public:
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
ec = error::expects_map_like_aggregate;
|
||||
ec = error::expects_map_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_map_like_aggregate;
|
||||
ec = adapter::error::expects_map_type;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -267,11 +292,11 @@ public:
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = adapter::error::expects_aggregate;
|
||||
ec = adapter::error::expects_aggregate_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(nd.aggregate_size == 1);
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
from_string(result.at(i_), nd.value, ec);
|
||||
}
|
||||
|
||||
@@ -295,9 +320,9 @@ struct list_impl {
|
||||
return;
|
||||
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
assert(nd.aggregate_size == 1);
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_aggregate;
|
||||
ec = adapter::error::expects_aggregate_type;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +390,7 @@ public:
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
assert(result_);
|
||||
BOOST_ASSERT(result_);
|
||||
impl_(*result_, nd, ec);
|
||||
}
|
||||
};
|
||||
@@ -399,3 +424,5 @@ public:
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
@@ -20,29 +20,20 @@
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
/** @brief Traits class for response objects.
|
||||
* @ingroup any
|
||||
/* Traits class for response objects.
|
||||
*
|
||||
* Provides traits for all supported response types i.e. all STL containers
|
||||
* and C++ buil-in types.
|
||||
* Provides traits for all supported response types i.e. all STL
|
||||
* containers and C++ buil-in types.
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits
|
||||
{
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::wrapper<ResponseType>;
|
||||
|
||||
/** @brief Returns an adapter for the reponse r
|
||||
*
|
||||
* @param r The response object e.g a C++ container.
|
||||
* @return An adapter suitable for use in resp3::read or resp3::async_read.
|
||||
* @remark Users can also use the free adapt function for type deduction.
|
||||
*/
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
@@ -96,94 +87,27 @@ struct assigner<0> {
|
||||
}
|
||||
};
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner2 {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
std::get<N>(dest) = internal_adapt(std::get<N>(from));
|
||||
assigner2<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner2<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
std::get<0>(dest) = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Return a specific adapter from the tuple.
|
||||
*
|
||||
* \param t A tuple of response adapters.
|
||||
* \return The adapter that corresponds to type T.
|
||||
*/
|
||||
template <class T, class Tuple>
|
||||
auto& get(Tuple& t)
|
||||
{
|
||||
return std::get<typename response_traits<T>::adapter_type>(t);
|
||||
}
|
||||
|
||||
template <class Tuple>
|
||||
using adapters_array_t =
|
||||
std::array<
|
||||
boost::mp11::mp_unique<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
template <class Tuple>
|
||||
adapters_array_t<Tuple> make_adapters_array(Tuple& t)
|
||||
{
|
||||
adapters_array_t<Tuple> ret;
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(ret, t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** @brief Transaforms a tuple of responses.
|
||||
*
|
||||
* @return Transaforms a tuple of responses into a tuple of adapters.
|
||||
*/
|
||||
template <class Tuple>
|
||||
using adapters_tuple_t =
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
std::tuple>;
|
||||
|
||||
/** @brief Make a tuple of adapters.
|
||||
*
|
||||
* \param t Tuple of responses.
|
||||
* \return Tuple of adapters.
|
||||
*/
|
||||
template <class Tuple>
|
||||
auto
|
||||
make_adapters_tuple(Tuple& t)
|
||||
{
|
||||
adapters_tuple_t<Tuple> ret;
|
||||
detail::assigner2<std::tuple_size<Tuple>::value - 1>::assign(ret, t);
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Tuple>
|
||||
class static_aggregate_adapter {
|
||||
private:
|
||||
using adapters_array_type =
|
||||
std::array<
|
||||
boost::mp11::mp_unique<
|
||||
boost::mp11::mp_rename<
|
||||
boost::mp11::mp_transform<
|
||||
adapter_t, Tuple>,
|
||||
boost::variant2::variant>>,
|
||||
std::tuple_size<Tuple>::value>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
adapters_array_t<Tuple> adapters_;
|
||||
adapters_array_type adapters_;
|
||||
|
||||
public:
|
||||
static_aggregate_adapter(Tuple* r = nullptr)
|
||||
: adapters_(make_adapters_array(*r))
|
||||
{}
|
||||
{
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
|
||||
}
|
||||
|
||||
void count(resp3::node<boost::string_view> const& nd)
|
||||
{
|
||||
@@ -230,5 +154,8 @@ struct response_traits<std::tuple<Ts...>>
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
@@ -1,18 +1,18 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_ADAPTER_ERROR_HPP
|
||||
#define AEDIS_ADAPTER_ERROR_HPP
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
/** \brief Adapter errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
@@ -21,13 +21,13 @@ enum class error
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Expects aggregate type.
|
||||
expects_aggregate,
|
||||
expects_aggregate_type,
|
||||
|
||||
/// Expects a map but got other aggregate.
|
||||
expects_map_like_aggregate,
|
||||
expects_map_type,
|
||||
|
||||
/// Expects a set aggregate but got something else.
|
||||
expects_set_aggregate,
|
||||
expects_set_type,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_aggregate_unsupported,
|
||||
@@ -41,20 +41,18 @@ enum class error
|
||||
/// Aggregate container has incompatible size.
|
||||
incompatible_size,
|
||||
|
||||
/// Not a double
|
||||
not_a_double,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
/** \brief todo
|
||||
/** \brief Creates a error_code object from an error.
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
/** \brief todo
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_condition make_error_condition(error e);
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
@@ -64,3 +62,5 @@ template<>
|
||||
struct is_error_code_enum<::aedis::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_ADAPTER_ERROR_HPP
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <system_error>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
@@ -23,16 +21,17 @@ struct error_category_impl : boost::system::error_category {
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type";
|
||||
case error::expects_aggregate: return "Expects aggregate type";
|
||||
case error::expects_map_like_aggregate: return "Expects map aggregate";
|
||||
case error::expects_set_aggregate: return "Expects set aggregate";
|
||||
case error::expects_simple_type: return "Expects a simple RESP3 type.";
|
||||
case error::expects_aggregate_type: return "Expects aggregate type.";
|
||||
case error::expects_map_type: return "Expects map type.";
|
||||
case error::expects_set_type: return "Expects set type.";
|
||||
case error::nested_aggregate_unsupported: return "Nested aggregate unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_size: return "Aggregate container has incompatible size.";
|
||||
case error::not_a_double: return "Not a double.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -50,10 +49,5 @@ boost::system::error_code make_error_code(error e)
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
boost::system::error_condition make_error_condition(error e)
|
||||
{
|
||||
return boost::system::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
535
aedis/aedis.hpp
535
aedis/aedis.hpp
@@ -1,17 +1,18 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_HPP
|
||||
#define AEDIS_HPP
|
||||
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/adapt.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
#include <aedis/generic/error.hpp>
|
||||
#include <aedis/generic/client.hpp>
|
||||
#include <aedis/generic/serializer.hpp>
|
||||
|
||||
@@ -25,32 +26,35 @@
|
||||
that provides simple and efficient communication with a Redis
|
||||
server. Some of its distinctive features are
|
||||
|
||||
@li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/antirez/RESP3/blob/master/spec.md).
|
||||
@li Support for the latest version of the Redis communication protocol [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md).
|
||||
@li First class support for STL containers and C++ built-in types.
|
||||
@li Serialization and deserialization of your own data types that avoid unnecessary copies.
|
||||
@li Support for Redis [sentinel](https://redis.io/docs/manual/sentinel).
|
||||
@li Sync and async API.
|
||||
|
||||
In addition to that, Aedis provides a high level client that offers the following functionality
|
||||
In addition to that, Aedis provides a high-level client that offers the following functionality
|
||||
|
||||
@li Management of message queues.
|
||||
@li Simplified handling of server pushes.
|
||||
@li Zero asymptotic allocations by means of memory reuse.
|
||||
@li Healthy checks.
|
||||
|
||||
If you never heard about Redis the best place to start is on
|
||||
https://redis.io. Now let us have a look at the low-level API.
|
||||
If you are interested in a detailed comparison of Redis clients
|
||||
and the design rationale behind Aedis jump to \ref why-aedis. Now
|
||||
let us have a look at the low-level API.
|
||||
|
||||
\section low-level-api Low-level API
|
||||
|
||||
The low-level API is very useful for simple tasks, for example,
|
||||
assume we want to perform the following steps
|
||||
The low-level API is very useful for tasks that can be performed
|
||||
in short lived connections, for example, assume we want to perform
|
||||
the following steps
|
||||
|
||||
@li Set the value of a Redis key.
|
||||
@li Set the expiration of that key to two seconds.
|
||||
@li Get and return its old value.
|
||||
@li Quit
|
||||
|
||||
The async coroutine-based implementation of the steps above look like
|
||||
The coroutine-based asynchronous implementation of the steps above look like
|
||||
|
||||
@code
|
||||
net::awaitable<std::string> set(net::ip::tcp::endpoint ep)
|
||||
@@ -61,17 +65,19 @@
|
||||
tcp_socket socket{co_await net::this_coro::executor};
|
||||
co_await socket.async_connect(ep);
|
||||
|
||||
std::string request, read_buffer, response;
|
||||
std::string buffer, response;
|
||||
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::set, "key", "Value", "EX", "2", "get");
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
co_await net::async_write(socket, net::buffer(buffer));
|
||||
buffer.clear();
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Hello (ignored).
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer), adapt(response)); // Set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(read_buffer)); // Quit (ignored)
|
||||
auto dbuffer = net::dynamic_buffer(read_buffer);
|
||||
co_await resp3::async_read(socket, dbuffer); // Hello ignored.
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(response)); // Set
|
||||
co_await resp3::async_read(socket, dbuffer); // Quit ignored.
|
||||
|
||||
co_return response;
|
||||
}
|
||||
@@ -89,19 +95,25 @@
|
||||
|
||||
\subsection requests Requests
|
||||
|
||||
As stated above, request are created by defining a storage object
|
||||
and a serializer that knowns how to convert user data into valid
|
||||
RESP3 wire-format. Redis request are composed of one or more
|
||||
As stated above, requests are created by defining a storage object
|
||||
and a serializer that knows how to convert user data into valid
|
||||
RESP3 wire-format. They are composed of one or more
|
||||
commands (in Redis documentation they are called [pipelines](https://redis.io/topics/pipelining)),
|
||||
which means users can add
|
||||
as many commands to the request as they like, a feature that aids
|
||||
performance.
|
||||
|
||||
The individual commands in a request assume many
|
||||
different forms: with and without keys, variable length arguments,
|
||||
ranges etc. To account for all these variations, the \c
|
||||
serializer class offers some member functions, each of
|
||||
them with a couple of overloads, for example
|
||||
different forms
|
||||
|
||||
@li With and without keys.
|
||||
@li Variable length arguments.
|
||||
@li Ranges.
|
||||
@li etc.
|
||||
|
||||
To account for all these variations, the \c serializer class
|
||||
offers some member functions, each of them with a couple of
|
||||
overloads, for example
|
||||
|
||||
@code
|
||||
// Some data to send to Redis.
|
||||
@@ -133,8 +145,8 @@
|
||||
sr.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
@endcode
|
||||
|
||||
Once all commands we want to send have been added to the
|
||||
request, we can write it as usual to the socket.
|
||||
Once all commands have been added to the request, we can write it
|
||||
as usual by writing the payload to the socket
|
||||
|
||||
@code
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
@@ -154,8 +166,10 @@
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
// Convert to obj string and call
|
||||
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
|
||||
// Convert to obj string and call to_bulk (see also add_header
|
||||
// and add_separator)
|
||||
auto dummy = "Dummy serializaiton string.";
|
||||
aedis::resp3::to_bulk(to, dummy);
|
||||
}
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
@@ -168,7 +182,7 @@
|
||||
|
||||
It is quite common to store json string in Redis for example.
|
||||
|
||||
\subsection responses Responses
|
||||
\subsection low-level-responses Responses
|
||||
|
||||
To read responses effectively, users must know their RESP3 type,
|
||||
this can be found in the Redis documentation of each command
|
||||
@@ -178,68 +192,70 @@
|
||||
---------|-------------------------------------|--------------
|
||||
lpush | Number | https://redis.io/commands/lpush
|
||||
lrange | Array | https://redis.io/commands/lrange
|
||||
set | Simple string, null or blob string | https://redis.io/commands/set
|
||||
get | Blob string | https://redis.io/commands/get
|
||||
set | Simple-string, null or blob-string | https://redis.io/commands/set
|
||||
get | Blob-string | https://redis.io/commands/get
|
||||
smembers | Set | https://redis.io/commands/smembers
|
||||
hgetall | Map | https://redis.io/commands/hgetall
|
||||
|
||||
Once the RESP3 type of a given response is known we can choose a
|
||||
proper C++ data structure to receive it in. Fortunately,
|
||||
this is a simple task for most types, for example
|
||||
proper C++ data structure to receive it in. Fortunately, this is a
|
||||
simple task for most types. The table below summarises the options
|
||||
|
||||
RESP3 type | C++ | Type
|
||||
---------------|--------------------------------------------------------------|------------------
|
||||
Simple string | \c std::string | Simple
|
||||
Simple error | \c std::string | Simple
|
||||
Blob string | \c std::string, \c std::vector | Simple
|
||||
Blob error | \c std::string, \c std::vector | Simple
|
||||
Number | `long long`, `int`, \c std::string | Simple
|
||||
Simple-string | \c std::string | Simple
|
||||
Simple-error | \c std::string | Simple
|
||||
Blob-string | \c std::string, \c std::vector | Simple
|
||||
Blob-error | \c std::string, \c std::vector | Simple
|
||||
Number | `long long`, `int`, `std::size_t`, \c std::string | Simple
|
||||
Double | `double`, \c std::string | Simple
|
||||
Null | `boost::optional<T>` | Simple
|
||||
Array | \c std::vector, \c std::list, \c std::array, \c std::deque | Aggregate
|
||||
Map | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
Set | \c std::vector, \c std::set, \c std::unordered_set | Aggregate
|
||||
Push | \c std::vector, \c std::map, \c std::unordered_map | Aggregate
|
||||
|
||||
Exceptions to this rule are responses that contain nested
|
||||
aggregates or heterogeneous data types, those will be treated
|
||||
later. As of this writing, not all RESP3 types are used by the
|
||||
Redis server, which means in practice users will be concerned with
|
||||
a reduced subset of the RESP3 specification. Now let us see some
|
||||
examples
|
||||
Responses that contain nested aggregates or heterogeneous data
|
||||
types will be given special treatment later. As of this writing,
|
||||
not all RESP3 types are used by the Redis server, which means in
|
||||
practice users will be concerned with a reduced subset of the
|
||||
RESP3 specification. Now let us see some examples
|
||||
|
||||
@code
|
||||
auto dbuffer = dynamic_buffer(buffer);
|
||||
|
||||
// To ignore the response.
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt());
|
||||
co_await resp3::async_read(socket, dbuffer, adapt());
|
||||
|
||||
// Read in a std::string e.g. get.
|
||||
std::string str;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(str));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(str));
|
||||
|
||||
// Read in a long long e.g. rpush.
|
||||
long long number;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(number));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(number));
|
||||
|
||||
// Read in a std::set e.g. smembers.
|
||||
std::set<T, U> set;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(set));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(set));
|
||||
|
||||
// Read in a std::map e.g. hgetall.
|
||||
std::map<T, U> set;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(map));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(map));
|
||||
|
||||
// Read in a std::unordered_map e.g. hgetall.
|
||||
std::unordered_map<T, U> umap;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(umap));
|
||||
|
||||
// Read in a std::vector e.g. lrange.
|
||||
std::vector<T> vec;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(vec));
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(vec));
|
||||
@endcode
|
||||
|
||||
In other words, it is pretty straightforward, just pass the result
|
||||
of \c adapt to the read function and make sure the response RESP3
|
||||
type fits in the type you are calling @c adapter(...) with. All
|
||||
standard C++ containers are supported by aedis.
|
||||
In other words, it is straightforward, just pass the result of \c
|
||||
adapt to the read function and make sure the response data type is
|
||||
compatible with the data structure you are calling @c adapter(...)
|
||||
with. All standard C++ containers are supported by Aedis.
|
||||
|
||||
\subsubsection Optional
|
||||
|
||||
@@ -253,9 +269,8 @@
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
@endcode
|
||||
|
||||
Everything else stays pretty much the same, before accessing data
|
||||
users will have to check (or assert) the optional contains a
|
||||
value.
|
||||
Everything else stays the same, before accessing data, users will
|
||||
have to check or assert the optional contains a value.
|
||||
|
||||
\subsubsection heterogeneous_aggregates Heterogeneous aggregates
|
||||
|
||||
@@ -263,7 +278,7 @@
|
||||
contain heterogeneous data, for example, an array that contains
|
||||
integers, strings nested sets etc. Aedis supports reading such
|
||||
aggregates in a \c std::tuple efficiently as long as the they
|
||||
don't contain 2-order nested aggregates e.g. an array that
|
||||
don't contain 3-order nested aggregates e.g. an array that
|
||||
contains an array of arrays. For example, to read the response to
|
||||
a \c hello command we can use the following response type.
|
||||
|
||||
@@ -305,11 +320,10 @@
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
|
||||
@endcode
|
||||
|
||||
Note that we are not ignoring the response to the commands
|
||||
themselves above but whether they have been successfully queued.
|
||||
Only after @c exec is received Redis will execute them in
|
||||
sequence. The response will then be sent in a single chunk to the
|
||||
client.
|
||||
Note that above we are not ignoring the response to the commands
|
||||
themselves but whether they have been successfully queued. Only
|
||||
after @c exec is received Redis will execute them in sequence and
|
||||
send all responses together in an array.
|
||||
|
||||
\subsubsection Serialization
|
||||
|
||||
@@ -318,7 +332,7 @@
|
||||
strings, for example
|
||||
|
||||
@code
|
||||
sr.push(command::set, "key", "json-string")
|
||||
sr.push(command::set, "key", "{"Server": "Redis"}"); // Unquoted for readability.
|
||||
sr.push(command::get, "key")
|
||||
@endcode
|
||||
|
||||
@@ -349,14 +363,13 @@
|
||||
|
||||
\subsubsection gen-case The general case
|
||||
|
||||
As already mentioned, there are cases where the response to Redis
|
||||
As already mentioned, there are cases where responses to Redis
|
||||
commands won't fit in the model presented above, some examples are
|
||||
|
||||
@li Commands (like \c set) whose response don't have a fixed
|
||||
RESP3 type. Expecting an \c int and receiving a blob string
|
||||
RESP3 type. Expecting an \c int and receiving a blob-string
|
||||
will result in error.
|
||||
@li RESP3 responses that contain three levels of (nested) aggregates can't be
|
||||
read in STL containers.
|
||||
@li RESP3 aggregates that contain nested aggregates can't be read in STL containers.
|
||||
@li Transactions with a dynamic number of commands can't be read in a \c std::tuple.
|
||||
|
||||
To deal with these cases Aedis provides the \c resp3::node
|
||||
@@ -425,63 +438,103 @@
|
||||
|
||||
\section high-level-api High-level API
|
||||
|
||||
It requires a lot of further work to make use of many important features
|
||||
of the Redis server while using the low-level API, for example
|
||||
As stated earlier, the low-level API is very useful for tasks that
|
||||
can be performed with short lived connections. Sometimes however,
|
||||
the need for long-lived connections becomes compeling
|
||||
|
||||
@li \b Server \b pushes: Short lived connections can't handle server pushes (e.g. https://redis.io/topics/client-side-caching and https://redis.io/topics/notifications).
|
||||
@li \b Pubsub: Just like server pushes, to use Redis pubsub users need long lasting connections (https://redis.io/topics/pubsub).
|
||||
@li \b Performance: Keep opening and closing connections impact performance.
|
||||
@li \b Pipeline: Code such as shown in \ref low-level-api don't
|
||||
support pipelines well since it can only send a fixed number of
|
||||
commands at time. It misses important optimization opportunities
|
||||
(https://redis.io/topics/pipelining).
|
||||
@li \b Server \b pushes: Short lived connections can't deal with server pushes, that means no [client side caching](https://redis.io/topics/client-side-caching), [notifications](https://redis.io/topics/notifications) and [pubsub](https://redis.io/topics/pubsub).
|
||||
@li \b Performance: Keep opening and closing connections impact performance serverely.
|
||||
@li \b Pipeline: Code such as shown in \ref low-level-api don't support pipelines well since it can only send a fixed number of commands at time. It misses important optimization opportunities (https://redis.io/topics/pipelining).
|
||||
|
||||
To avoid these drawbacks users will address the points above
|
||||
reinventing the high-level API here and there over and over again,
|
||||
to prevent that from happening Aedis provides its own. The general
|
||||
form of a program that uses the high-level api looks like this
|
||||
A serious implementation that supports the points listed above is
|
||||
far from trivial and involves many complex asynchronous operations
|
||||
|
||||
@li \c async_resolve: Resolve a hostname.
|
||||
@li \c async_connect: Connect to Redis.
|
||||
@li \c async_read: Performed in a loop as long as the connection lives.
|
||||
@li \c async_write: Performed everytime a new message is added.
|
||||
@li \c async_wait: To timout all operations above if the server becomes unresponsive.
|
||||
|
||||
Notice that many of the operations above will run concurrently with each other and, in addition to that
|
||||
|
||||
@li \c async_write operations require management of the message queue to prevent concurrent writes.
|
||||
@li Healthy checks must be sent periodically by the client to detect a dead or unresponsive server.
|
||||
@li Recovery after a disconnection to avoid loosing enqueued commands.
|
||||
|
||||
Expecting users to implement these points themselves is
|
||||
unrealistic and could result in code that performs poorly and
|
||||
can't handle errors properly. To avoid all of that, Aedis
|
||||
provides its own implementation. The general form of a program
|
||||
that uses the high-level API looks like this
|
||||
|
||||
@code
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client<net::ip::tcp::socket> db{ioc.get_executor()};
|
||||
receiver recv;
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ ... });
|
||||
client_type db(ioc.get_executor());
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db.set_receiver(recv);
|
||||
|
||||
// Pass db around to other objects so we can send commands.
|
||||
db.async_run("127.0.0.1", "6379", [](auto ec){ ... });
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@endcode
|
||||
|
||||
The only thing users have to care about is with the implementation
|
||||
of the \c receiver class, everything else will be performed
|
||||
automatically by the client class. The general form of a receiver
|
||||
looks like this
|
||||
Users are concerned only with the implementation of the
|
||||
receiver. For example
|
||||
|
||||
@code
|
||||
class receiver {
|
||||
public:
|
||||
// Called when a new chunck of user data becomes available.
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
|
||||
|
||||
// Called when a response becomes available.
|
||||
void on_read(command cmd);
|
||||
|
||||
// Called when a request has been writen to the socket.
|
||||
void on_write(std::size_t n);
|
||||
|
||||
// Called when a server push is received.
|
||||
void on_push();
|
||||
// Callbacks.
|
||||
struct receiver {
|
||||
void on_resp3(command cmd, node<string_view> const& nd, error_code& ec) { ... }
|
||||
void on_read(command cmd, std::size_t) { ... }
|
||||
void on_push(std::size_t n) { }
|
||||
void on_write(std::size_t n) { ... }
|
||||
};
|
||||
@endcode
|
||||
|
||||
Sending commands is also similar to what has been discussed before.
|
||||
The functions in the receiver above are callbacks that will be
|
||||
called when events arrives
|
||||
|
||||
@li \c on_resp3: Called when a new chunk of resp3 data is parsed.
|
||||
@li \c on_read: Called after the response to a command has been successfully read.
|
||||
@li \c on_push: Called when a server push is received.
|
||||
@li \c on_write: Called after a request has been successfully written to the stream.
|
||||
|
||||
The callbacks above are never called on errors, instead the \c
|
||||
async_run function returns. Reconnection is also supported, for
|
||||
example
|
||||
|
||||
@code
|
||||
net::awaitable<void> run(std::shared_ptr<client_type> db)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
boost::asio::steady_timer timer{ex};
|
||||
|
||||
for (error_code ec;;) {
|
||||
co_await db->async_run("127.0.0.1", "6379", redirect_error(use_awaitable, ec));
|
||||
|
||||
// Log the error.
|
||||
std::clog << ec.message() << std::endl;
|
||||
|
||||
// Wait two seconds and try again.
|
||||
timer.expires_after(std::chrono::seconds{2});
|
||||
co_await timer.async_wait(redirect_error(use_awaitable, ec));
|
||||
}
|
||||
}
|
||||
@endcode
|
||||
|
||||
when reconnecting the client will recover requests that haven't
|
||||
been sent to Redis yet.
|
||||
|
||||
\subsection high-level-sending-cmds Sending commands
|
||||
|
||||
The db object from the example above can be passed around to other
|
||||
objects so that commands can be sent from everywhere in the app.
|
||||
Sending commands is also similar to what has been discussed before
|
||||
|
||||
@code
|
||||
void foo(client<net::ip::tcp::socket>& db)
|
||||
@@ -495,81 +548,36 @@
|
||||
@endcode
|
||||
|
||||
The \c send functions in this case will add commands to the output
|
||||
queue and send them only if there is no pending response of a
|
||||
previously sent command. This is so because RESP3 is a
|
||||
request/response protocol, which means clients must wait for the
|
||||
response to a command before proceeding with the next one.
|
||||
|
||||
\subsection high-level-responses Responses
|
||||
|
||||
Aedis also provides some facilities to use use custom responses with the
|
||||
high-level API. Assume for example you have many different custom
|
||||
response types \c T1, \c T2 etc, a receiver that makes use of this looks
|
||||
like
|
||||
|
||||
@code
|
||||
using responses_tuple_type = std::tuple<T1, T2, T3>;
|
||||
using adapters_tuple_type = adapters_t<responses_tuple_type>;
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver(...) : adapters_(make_adapters_tuple(resps_)) , {}
|
||||
|
||||
void
|
||||
on_resp3( command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
// Direct the responses to the desired adapter.
|
||||
switch (cmd) {
|
||||
case cmd1: adapter::get<T1>(adapters_)(nd, ec);
|
||||
case cmd2: adapter::get<T2>(adapters_)(nd, ec);
|
||||
case cmd3: adapter::get<T2>(adapters_)(nd, ec);
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case cmd1: // Data on std::get<T1>(resps_); break;
|
||||
case cmd2: // Data on std::get<T2>(resps_); break;
|
||||
case cmd3: // Data on std::get<T3>(resps_); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n) { ... }
|
||||
|
||||
void on_push() { ... }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
};
|
||||
@endcode
|
||||
queue and send them only if there is no pending response. This is
|
||||
so because RESP3 is a request/response protocol, which means
|
||||
clients must wait for responses before sending
|
||||
the next request.
|
||||
|
||||
\section examples Examples
|
||||
|
||||
To better fix what has been said above, users should have a look at some simple examples.
|
||||
|
||||
\b Low \b level \b API
|
||||
\b Low \b level \b API (sync)
|
||||
|
||||
@li low_level/sync_intro.cpp: Shows how to use the Aedis synchronous api.
|
||||
@li low_level/async_intro.cpp: Show how to use the low level async api.
|
||||
@li low_level/subscriber.cpp: Shows how channel subscription works at the low level.
|
||||
@li low_level/adapter.cpp: Shows how to write a response adapter that prints to the screen, see \ref low-level-adapters.
|
||||
@li intro_sync.cpp: Synchronous API usage example.
|
||||
@li serialization_sync.cpp: Shows how serialize your own types.
|
||||
|
||||
\b High \b level \b API
|
||||
\b Low \b level \b API (async-coroutine)
|
||||
|
||||
@li high_level/intro.cpp: Some commands are sent to the Redis server and the responses are printed to screen.
|
||||
@li high_level/aggregates.cpp: Shows how receive RESP3 aggregate data types in a general way.
|
||||
@li high_level/stl_containers.cpp: Shows how to read responses in STL containers.
|
||||
@li high_level/serialization.cpp: Shows how to de/serialize your own data types.
|
||||
@li high_level/subscriber.cpp: Shows how channel subscription works at a high level. See also https://redis.io/topics/pubsub.
|
||||
@li subscriber.cpp: Shows how channel subscription works at the low level.
|
||||
@li transaction.cpp: Shows how to read the response to transactions.
|
||||
@li custom_adapter.cpp: Shows how to write a response adapter that prints to the screen, see \ref low-level-adapters.
|
||||
|
||||
\b Asynchronous \b Servers
|
||||
\b High \b level \b API (async only)
|
||||
|
||||
@li high_level/echo_server.cpp: Shows the basic principles behind asynchronous communication with a database in an asynchronous server.
|
||||
@li high_level/chat_room.cpp: Shows how to build a scalable chat room that scales to millions of users.
|
||||
@li intro_high_level.cpp: High-level API usage example.
|
||||
@li aggregates_high_level.cpp: Shows how receive RESP3 aggregate data types in a general way or in STL containers.
|
||||
@li subscriber_high_level.cpp: Shows how channel [subscription](https://redis.io/topics/pubsub) works at a high-level.
|
||||
|
||||
\b Asynchronous \b Servers (high-level API)
|
||||
|
||||
@li echo_server.cpp: Shows the basic principles behind asynchronous communication with a database in an asynchronous server.
|
||||
@li chat_room.cpp: Shows how to build a scalable chat room.
|
||||
|
||||
\section using-aedis Using Aedis
|
||||
|
||||
@@ -603,7 +611,7 @@
|
||||
```
|
||||
|
||||
If you can't use \c configure and \c make (e.g. Windows users)
|
||||
you can already add the directory where you unpacked aedis to the
|
||||
you can already add the directory where you unpacked Aedis to the
|
||||
include directories in your project, otherwise run
|
||||
|
||||
```
|
||||
@@ -650,11 +658,190 @@
|
||||
$ make distcheck
|
||||
```
|
||||
|
||||
\section Referece
|
||||
\section why-aedis Why Aedis
|
||||
|
||||
At the time of this writing there are seventeen Redis clients
|
||||
listed in the [official](https://redis.io/docs/clients/#cpp) list.
|
||||
With so many clients available it is not unlikely that users are
|
||||
asking themselves why yet another one. In this section I will try
|
||||
to compare Aedis to the most popular clients and why we need
|
||||
Aedis. Notice however that this is ongoing work as comparing
|
||||
client objectively is difficult and time consuming.
|
||||
|
||||
The most popular client at the moment of this writing ranked by
|
||||
github stars is
|
||||
|
||||
@li https://github.com/sewenew/redis-plus-plus
|
||||
|
||||
Before we start it is worth mentioning some of the things it does
|
||||
not support
|
||||
|
||||
@li RESP3. Without RESP3 is impossible to support some important
|
||||
Redis features like client side caching, among other things.
|
||||
@li The Asio asynchronous model.
|
||||
@li Serialization of user data types that avoids temporaries.
|
||||
@li Error handling with error-code and exception overloads.
|
||||
@li Healthy checks.
|
||||
@li Fine control over memory allocation by means of allocators.
|
||||
|
||||
The remaining points will be addressed individually.
|
||||
|
||||
@subsection redis-plus-plus
|
||||
|
||||
Let us first have a look at what sending a command a pipeline and a
|
||||
transaction look like
|
||||
|
||||
@code
|
||||
auto redis = Redis("tcp://127.0.0.1:6379");
|
||||
|
||||
// Send commands
|
||||
redis.set("key", "val");
|
||||
auto val = redis.get("key"); // val is of type OptionalString.
|
||||
if (val)
|
||||
std::cout << *val << std::endl;
|
||||
|
||||
// Sending pipelines
|
||||
auto pipe = redis.pipeline();
|
||||
auto pipe_replies = pipe.set("key", "value")
|
||||
.get("key")
|
||||
.rename("key", "new-key")
|
||||
.rpush("list", {"a", "b", "c"})
|
||||
.lrange("list", 0, -1)
|
||||
.exec();
|
||||
|
||||
// Parse reply with reply type and index.
|
||||
auto set_cmd_result = pipe_replies.get<bool>(0);
|
||||
// ...
|
||||
|
||||
// Sending a transaction
|
||||
auto tx = redis.transaction();
|
||||
auto tx_replies = tx.incr("num0")
|
||||
.incr("num1")
|
||||
.mget({"num0", "num1"})
|
||||
.exec();
|
||||
|
||||
auto incr_result0 = tx_replies.get<long long>(0);
|
||||
// ...
|
||||
@endcode
|
||||
|
||||
Some of the problems with this API are
|
||||
|
||||
@li Heterogeneous treatment of commands, pipelines and transaction.
|
||||
@li Having to manually finish the pipeline with \c .exec() is a major source of headache. This is not required by the protocol itself but results from the abstraction used.
|
||||
@li Any Api that sends individual commands has a very restricted scope of usability and should be avoided in anything that needs minimum performance guarantees.
|
||||
@li The API imposes exceptions on users, no error-code overload is provided.
|
||||
@li No control over dynamic allocations.
|
||||
@li No way to reuse the buffer for new calls to e.g. \c redis.get in order to avoid further dynamic memory allocations.
|
||||
@li Error handling of resolve and connection no clear.
|
||||
|
||||
According to the documentation, pipelines in redis-plus-plus have
|
||||
the following characteristics
|
||||
|
||||
> NOTE: By default, creating a Pipeline object is NOT cheap, since
|
||||
> it creates a new connection.
|
||||
|
||||
This is clearly a downside of the API as pipelines should be the
|
||||
default way of communicating and not an exception, paying such a
|
||||
high price for each pipeline imposes a severe cost in performance.
|
||||
Transactions also suffer from the very same problem
|
||||
|
||||
> NOTE: Creating a Transaction object is NOT cheap, since it
|
||||
> creates a new connection.
|
||||
|
||||
In Aedis there is no difference between sending one command, a
|
||||
pipeline or a transaction because creating the request is decoupled
|
||||
from the IO objects, for example
|
||||
|
||||
@code
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping, "Some message.");
|
||||
sr.push(command::set, "low-level-key", "some content", "EX", "2");
|
||||
sr.push(command::exec);
|
||||
sr.push(command::ping, "Another message.");
|
||||
net::write(socket, net::buffer(request));
|
||||
@endcode
|
||||
|
||||
The request created above will be sent to Redis in a single
|
||||
pipeline and imposes no restriction on what it contains e.g. the
|
||||
number of commands, transactions etc. The problems mentioned above
|
||||
simply do not exist in Aedis. The way responses are read is
|
||||
also more flexible
|
||||
|
||||
@code
|
||||
std::string buffer;
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
|
||||
std::tuple<std::string, boost::optional<std::string>> response;
|
||||
resp3::read(socket, dbuffer); // hellp
|
||||
resp3::read(socket, dbuffer); // multi
|
||||
resp3::read(socket, dbuffer); // ping
|
||||
resp3::read(socket, dbuffer); // set
|
||||
resp3::read(socket, dbuffer, adapt(response));
|
||||
resp3::read(socket, dbuffer); // quit
|
||||
@endcode
|
||||
|
||||
@li The response objects are passed by the caller to the read
|
||||
functions so that he has fine control over memory allocations and
|
||||
object lifetime.
|
||||
@li The user can either use error-code or exceptions.
|
||||
@li Each response can be read individually in the response object
|
||||
avoiding temporaries.
|
||||
@li It is possible to ignore responses.
|
||||
|
||||
This was the blocking API, now let us compare the async interface
|
||||
|
||||
> redis-plus-plus also supports async interface, however, async
|
||||
> support for Transaction and Subscriber is still on the way.
|
||||
>
|
||||
> The async interface depends on third-party event library, and so
|
||||
> far, only libuv is supported.
|
||||
|
||||
Async code in redis-plus-plus looks like the following
|
||||
|
||||
@code
|
||||
auto async_redis = AsyncRedis(opts, pool_opts);
|
||||
|
||||
Future<string> ping_res = async_redis.ping();
|
||||
|
||||
cout << ping_res.get() << endl;
|
||||
@endcode
|
||||
|
||||
As the reader can see, the async interface is based on futures
|
||||
which is also known to have a bad performance. The biggest
|
||||
problem however with this async design is that it makes it
|
||||
impossible to write asynchronous programs correctly since it
|
||||
starts an async operation on every command sent instead of
|
||||
enqueueing a message and triggering a write. It is also not clear
|
||||
how are pipelines realised with the design (if at all).
|
||||
|
||||
In Aedis the send function looks like this
|
||||
|
||||
@code
|
||||
template <class... Ts>
|
||||
void client::send(Command cmd, Ts const&... args);
|
||||
@endcode
|
||||
|
||||
and the response is delivered through a callback.
|
||||
|
||||
\section Acknowledgement
|
||||
|
||||
Some people that were helpful in the development of Aedis
|
||||
|
||||
@li Richard Hodges ([madmongo1](https://github.com/madmongo1)): For answering pretty much every question I had about Asio and the design of asynchronous programs.
|
||||
@li Vinícius dos Santos Oliveira ([vinipsmaker](https://github.com/vinipsmaker)): For useful discussion about how Aedis consumes buffers in the read operation (among other things).
|
||||
|
||||
\section Reference
|
||||
|
||||
See \subpage any.
|
||||
|
||||
*/
|
||||
|
||||
/** \defgroup any Reference
|
||||
*
|
||||
* This page contains the documentation of all user facing code.
|
||||
*/
|
||||
|
||||
#endif // AEDIS_HPP
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_GENERIC_CLIENT_HPP
|
||||
#define AEDIS_GENERIC_CLIENT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/generic/detail/client_ops.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
// TODO: What to do if users send a discard command not contained in a
|
||||
// transaction. The client object will try to pop the queue until a
|
||||
// multi is found.
|
||||
#include <aedis/generic/detail/client_ops.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
@@ -26,80 +29,114 @@ namespace generic {
|
||||
/** \brief A high level Redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This class represents a connection to the Redis server. Some of
|
||||
* its most important features are
|
||||
*
|
||||
* 1. Automatic management of commands. The implementation will send
|
||||
* commands and read responses automatically for the user.
|
||||
* 2. Memory reuse. Dynamic memory allocations will decrease with time.
|
||||
*
|
||||
* For more details, please see the documentation of each individual
|
||||
* function.
|
||||
* This class keeps a connection open to the Redis server where
|
||||
* commands can be sent at any time. For more details, please see the
|
||||
* documentation of each individual function.
|
||||
*/
|
||||
template <class AsyncReadWriteStream, class Command>
|
||||
class client {
|
||||
public:
|
||||
using stream_type = AsyncReadWriteStream;
|
||||
using executor_type = typename stream_type::executor_type;
|
||||
/// Executor type.
|
||||
using executor_type = typename AsyncReadWriteStream::executor_type;
|
||||
|
||||
/// Callback type of read operations.
|
||||
using read_handler_type = std::function<void(Command cmd, std::size_t)>;
|
||||
|
||||
/// Callback type of write operations.
|
||||
using write_handler_type = std::function<void(std::size_t)>;
|
||||
|
||||
/// Callback type of push operations.
|
||||
using push_handler_type = std::function<void(std::size_t)>;
|
||||
|
||||
/// Callback type of resp3 operations.
|
||||
using resp3_handler_type = std::function<void(Command, resp3::node<boost::string_view> const&, boost::system::error_code&)>;
|
||||
|
||||
using default_completion_token_type = boost::asio::default_completion_token_t<executor_type>;
|
||||
|
||||
/** @brief Configuration parameters.
|
||||
*/
|
||||
struct config {
|
||||
/// Timeout of the \c async_resolve operation.
|
||||
std::chrono::seconds resolve_timeout = std::chrono::seconds{5};
|
||||
|
||||
/// Timeout of the \c async_connect operation.
|
||||
std::chrono::seconds connect_timeout = std::chrono::seconds{5};
|
||||
|
||||
/// Timeout of the \c async_read operation.
|
||||
std::chrono::seconds read_timeout = std::chrono::seconds{5};
|
||||
|
||||
/// Timeout of the \c async_write operation.
|
||||
std::chrono::seconds write_timeout = std::chrono::seconds{5};
|
||||
|
||||
/// Time after which a connection is considered idle if no data is received.
|
||||
std::chrono::seconds idle_timeout = std::chrono::seconds{10};
|
||||
|
||||
/// The maximum size allwed in a read operation.
|
||||
std::size_t max_read_size = (std::numeric_limits<std::size_t>::max)();
|
||||
};
|
||||
|
||||
/** \brief Constructor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
* \param cfg Configuration parameters.
|
||||
*/
|
||||
client(boost::asio::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
client(boost::asio::any_io_executor ex, config cfg = config{})
|
||||
: resv_{ex}
|
||||
, read_timer_{ex}
|
||||
, write_timer_{ex}
|
||||
, wait_write_timer_{ex}
|
||||
, check_idle_timer_{ex}
|
||||
, cfg_{cfg}
|
||||
, on_read_{[](Command, std::size_t){}}
|
||||
, on_write_{[](std::size_t){}}
|
||||
, on_push_{[](std::size_t){}}
|
||||
, on_resp3_{[](Command, resp3::node<boost::string_view> const&, boost::system::error_code&) {}}
|
||||
, sr_{requests_}
|
||||
, last_data_{std::chrono::time_point<std::chrono::steady_clock>::min()}
|
||||
, type_{resp3::type::invalid}
|
||||
, cmd_info_{std::make_pair<Command>(Command::invalid, 0)}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
send(Command::hello, 3);
|
||||
if (cfg.idle_timeout < std::chrono::seconds{2})
|
||||
cfg.idle_timeout = std::chrono::seconds{2};
|
||||
}
|
||||
|
||||
/// Returns the executor.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
auto get_executor() {return read_timer_.get_executor();}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command to send.
|
||||
* @param args Arguments to commands.
|
||||
* Adds a command to the end of the next request and signals the
|
||||
* writer operation there is a new message awaiting to be sent.
|
||||
* Otherwise the function is equivalent to serializer::push. @sa
|
||||
* serializer.
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push(cmd, args...);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
sr_.push(cmd, args...);
|
||||
auto const d = requests_.size() - before;
|
||||
BOOST_ASSERT(d != 0);
|
||||
info_.back().size += d;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
commands_.push_back(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param key The key the commands refers to
|
||||
* @param begin Begin of the range.
|
||||
* @param end End of the range.
|
||||
* Adds a command to the end of the next request and signals the
|
||||
* writer operation there is a new message awaiting to be sent.
|
||||
* Otherwise the function is equivalent to
|
||||
* serializer::push_range2.
|
||||
* @sa serializer.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void send_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
@@ -109,32 +146,28 @@ public:
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push_range2(cmd, key, begin, end);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
sr_.push_range2(cmd, key, begin, end);
|
||||
auto const d = requests_.size() - before;
|
||||
BOOST_ASSERT(d != 0);
|
||||
info_.back().size += d;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
commands_.push_back(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param begin Begin of the range.
|
||||
* @param end End of the range.
|
||||
* Adds a command to the end of the next request and signals the
|
||||
* writer operation there is a new message awaiting to be sent.
|
||||
* Otherwise the function is equivalent to
|
||||
* serializer::push_range2.
|
||||
* @sa serializer.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void send_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
@@ -144,32 +177,28 @@ public:
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
serializer<std::string> sr(requests_);
|
||||
auto const before = requests_.size();
|
||||
sr.push_range2(cmd, begin, end);
|
||||
auto const after = requests_.size();
|
||||
assert(after - before != 0);
|
||||
req_info_.front().size += after - before;;
|
||||
sr_.push_range2(cmd, begin, end);
|
||||
auto const d = requests_.size() - before;
|
||||
BOOST_ASSERT(d != 0);
|
||||
info_.back().size += d;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.push_back(cmd);
|
||||
++req_info_.front().cmds;
|
||||
commands_.push_back(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param key The key the commands refers to.
|
||||
* @param range Range of elements to send.
|
||||
* Adds a command to the end of the next request and signals the
|
||||
* writer operation there is a new message awaiting to be sent.
|
||||
* Otherwise the function is equivalent to
|
||||
* serializer::push_range.
|
||||
* @sa serializer.
|
||||
*/
|
||||
template <class Key, class Range>
|
||||
void send_range(Command cmd, Key const& key, Range const& range)
|
||||
@@ -181,13 +210,11 @@ public:
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* Adds a command to the output command queue and signals the write
|
||||
* operation there are new messages awaiting to be sent to Redis.
|
||||
*
|
||||
* @sa serializer.hpp
|
||||
*
|
||||
* @param cmd The command.
|
||||
* @param range End of the range.
|
||||
* Adds a command to the end of the next request and signals the
|
||||
* writer operation there is a new message awaiting to be sent.
|
||||
* Otherwise the function is equivalent to
|
||||
* serializer::push_range.
|
||||
* @sa serializer.
|
||||
*/
|
||||
template <class Range>
|
||||
void send_range(Command cmd, Range const& range)
|
||||
@@ -199,176 +226,476 @@ public:
|
||||
|
||||
/** @brief Starts communication with the Redis server asynchronously.
|
||||
*
|
||||
* This class performs the following steps
|
||||
* This function performs the following steps
|
||||
*
|
||||
* @li Connect to the endpoint passed in the function parameter.
|
||||
* @li Start the async read operation that keeps reading responses to commands and server pushes.
|
||||
* @li Start the async write operation that keeps sending commands to Redis.
|
||||
* @li Resolves the Redis host as of \c async_resolve with the
|
||||
* timeout passed in client::config::resolve_timeout.
|
||||
*
|
||||
* \param recv The receiver (see below)
|
||||
* \param ep The address of the Redis server.
|
||||
* \param token The completion token (ASIO jargon)
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in client::config::connect_timeout.
|
||||
*
|
||||
* The receiver is a class that privides the following member functions
|
||||
* @li Starts the \c async_read operation that keeps reading incoming
|
||||
* responses. Each individual read uses the timeout passed on
|
||||
* client::config::read_timeout. After each successful read it
|
||||
* will call the read or push callback.
|
||||
*
|
||||
* @li Starts the \c async_write operation that waits for new commands
|
||||
* to be sent to Redis. Each individual write uses the timeout
|
||||
* passed on client::config::write_timeout. After a successful
|
||||
* write it will call the write callback.
|
||||
*
|
||||
* @li Starts the check idle operation with the timeout specified
|
||||
* in client::config::idle_timeout. If no data is received during
|
||||
* that time interval \c async_run completes with
|
||||
* generic::error::idle_timeout.
|
||||
*
|
||||
* @li Starts the healthy check operation that sends
|
||||
* redis::command::ping to Redis with a frequency equal to
|
||||
* client::config::idle_timeout / 2.
|
||||
*
|
||||
* In addition to the callbacks mentioned above, the read
|
||||
* operations will call the resp3 callback as soon a new chunks of
|
||||
* data become available to the user.
|
||||
*
|
||||
* It is safe to call \c async_run after it has returned. In this
|
||||
* case, any outstanding commands will be sent after the
|
||||
* connection is restablished. If a disconnect occurs while the
|
||||
* response to a request has not been received, the client doesn't
|
||||
* try to resend it to avoid resubmission.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* class receiver {
|
||||
* public:
|
||||
* // Called when a new chunck of user data becomes available.
|
||||
* void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec);
|
||||
*
|
||||
* // Called when a response becomes available.
|
||||
* void on_read(command cmd);
|
||||
*
|
||||
* // Called when a request has been writen to the socket.
|
||||
* void on_write(std::size_t n);
|
||||
*
|
||||
* // Called when a server push is received.
|
||||
* void on_push();
|
||||
* };
|
||||
* awaitable<void> run_with_reconnect(std::shared_ptr<client_type> db)
|
||||
* {
|
||||
* auto ex = co_await this_coro::executor;
|
||||
* asio::steady_timer timer{ex};
|
||||
*
|
||||
* for (error_code ec;;) {
|
||||
* co_await db->async_run("127.0.0.1", "6379", redirect_error(use_awaitable, ec));
|
||||
* timer.expires_after(std::chrono::seconds{2});
|
||||
* co_await timer.async_wait(redirect_error(use_awaitable, ec));
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* \param host Ip address or name of the Redis server.
|
||||
* \param port Port where the Redis server is listening.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
*
|
||||
* @code
|
||||
* void f(boost::system::error_code);
|
||||
* @endcode
|
||||
*
|
||||
* \return This function returns only when there is an error.
|
||||
*/
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type
|
||||
>
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_run(
|
||||
Receiver& recv,
|
||||
boost::asio::ip::tcp::endpoint ep = {boost::asio::ip::make_address("127.0.0.1"), 6379},
|
||||
boost::string_view host = "127.0.0.1",
|
||||
boost::string_view port = "6379",
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
endpoint_ = ep;
|
||||
host_ = host;
|
||||
port_ = port;
|
||||
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(run_op<client, Receiver>{this, &recv}, token, socket_, timer_);
|
||||
>(detail::run_op<client>{this}, token, read_timer_, write_timer_, wait_write_timer_);
|
||||
}
|
||||
|
||||
/// Set the read handler.
|
||||
void set_read_handler(read_handler_type rh)
|
||||
{ on_read_ = std::move(rh); }
|
||||
|
||||
/// Set the write handler.
|
||||
void set_write_handler(write_handler_type wh)
|
||||
{ on_write_ = std::move(wh); }
|
||||
|
||||
/// Set the push handler.
|
||||
void set_push_handler(push_handler_type ph)
|
||||
{ on_push_ = std::move(ph); }
|
||||
|
||||
/// Set the resp3 handler.
|
||||
void set_resp3_handler(resp3_handler_type rh)
|
||||
{ on_resp3_ = std::move(rh); }
|
||||
|
||||
/** @brief Convenience callback setter.
|
||||
*
|
||||
* Expects a class with the following member functions
|
||||
*
|
||||
* @code
|
||||
* struct receiver {
|
||||
* void on_resp3(Command cmd, resp3::node<boost::string_view> const& nd, boost::system::error_code& ec);
|
||||
* void on_read(Command cmd, std::size_t);
|
||||
* void on_write(std::size_t n);
|
||||
* void on_push(std::size_t n);
|
||||
* };
|
||||
* @endcode
|
||||
*/
|
||||
template <class Receiver>
|
||||
void set_receiver(std::shared_ptr<Receiver> recv)
|
||||
{
|
||||
on_resp3_ = [recv](Command cmd, resp3::node<boost::string_view> const& nd, boost::system::error_code& ec){recv->on_resp3(cmd, nd, ec);};
|
||||
on_read_ = [recv](Command cmd, std::size_t n){recv->on_read(cmd, n);};
|
||||
on_write_ = [recv](std::size_t n){recv->on_write(n);};
|
||||
on_push_ = [recv](std::size_t n){recv->on_push(n);};
|
||||
}
|
||||
|
||||
private:
|
||||
template <class T, class U, class V> friend struct read_op;
|
||||
template <class T, class U> friend struct writer_op;
|
||||
template <class T, class U> friend struct read_write_op;
|
||||
template <class T, class U> friend struct run_op;
|
||||
using command_info_type = std::pair<Command, std::size_t>;
|
||||
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
template <class T, class V> friend struct detail::reader_op;
|
||||
template <class T, class V> friend struct detail::ping_after_op;
|
||||
template <class T> friend struct detail::read_op;
|
||||
template <class T> friend struct detail::read_until_op;
|
||||
template <class T> friend struct detail::writer_op;
|
||||
template <class T> friend struct detail::write_op;
|
||||
template <class T> friend struct detail::run_op;
|
||||
template <class T> friend struct detail::connect_op;
|
||||
template <class T> friend struct detail::resolve_op;
|
||||
template <class T> friend struct detail::check_idle_op;
|
||||
template <class T> friend struct detail::init_op;
|
||||
template <class T> friend struct detail::read_write_check_op;
|
||||
template <class T> friend struct detail::wait_for_data_op;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
void on_resolve()
|
||||
{
|
||||
// If we are coming from a connection that was lost we have to
|
||||
// reset the socket to a fresh state.
|
||||
socket_ =
|
||||
std::make_shared<AsyncReadWriteStream>(read_timer_.get_executor());
|
||||
}
|
||||
|
||||
// Buffer used in the read operations.
|
||||
std::string read_buffer_;
|
||||
void on_connect()
|
||||
{
|
||||
// When we are reconnecting we can't simply call send(hello)
|
||||
// as that will add the command to the end of the queue, we need
|
||||
// it as the first element.
|
||||
if (info_.empty()) {
|
||||
// Either we are connecting for the first time or there are
|
||||
// no commands that were left unresponded from the last
|
||||
// connection. We can send hello as usual.
|
||||
BOOST_ASSERT(requests_.empty());
|
||||
BOOST_ASSERT(commands_.empty());
|
||||
send(Command::hello, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
if (info_.front().sent) {
|
||||
// There is one request that was left unresponded when we
|
||||
// e.g. lost the connection, since we erase requests right
|
||||
// after writing them to the socket (to avoid resubmission) it
|
||||
// is lost and we have to remove it.
|
||||
|
||||
// Noop if info_.front().size is already zero, which happens
|
||||
// when the request was successfully writen to the socket.
|
||||
// In the future we may want to avoid erasing but resend (at
|
||||
// the risc of resubmission).
|
||||
requests_.erase(0, info_.front().size);
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::vector<Command> commands_;
|
||||
// Erases the commands that were lost as well.
|
||||
commands_.erase(
|
||||
std::begin(commands_),
|
||||
std::begin(commands_) + info_.front().cmds);
|
||||
|
||||
// Info about the requests.
|
||||
std::vector<request_info> req_info_;
|
||||
info_.front().cmds = 0;
|
||||
|
||||
// The stream.
|
||||
stream_type socket_;
|
||||
// Do not erase the info_ front as we will use it below.
|
||||
// info_.erase(std::begin(info_));
|
||||
}
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
boost::asio::steady_timer timer_;
|
||||
// Code below will add a hello to the front of the request and
|
||||
// update info_ and commands_ accordingly.
|
||||
|
||||
// Redis endpoint.
|
||||
boost::asio::ip::tcp::endpoint endpoint_;
|
||||
auto const old_size = requests_.size();
|
||||
sr_.push(Command::hello, 3);
|
||||
auto const hello_size = requests_.size() - old_size;;
|
||||
|
||||
bool stop_writer_ = false;
|
||||
// Now we have to rotate the hello to the front of the request
|
||||
// (Remember it must always be the first command).
|
||||
std::rotate(
|
||||
std::begin(requests_),
|
||||
std::begin(requests_) + old_size,
|
||||
std::end(requests_));
|
||||
|
||||
/* Prepares the back of the queue to receive further commands.
|
||||
*
|
||||
* If true is returned the request in the front of the queue can be
|
||||
* sent to the server. See async_write_some.
|
||||
*/
|
||||
// Updates info_.
|
||||
info_.front().size += hello_size;
|
||||
info_.front().cmds += 1;
|
||||
|
||||
// Updates commands_
|
||||
commands_.push_back(std::make_pair(Command::hello, hello_size));
|
||||
std::rotate(
|
||||
std::begin(commands_),
|
||||
std::prev(std::end(commands_)),
|
||||
std::end(commands_));
|
||||
}
|
||||
|
||||
// Prepares the back of the queue to receive further commands. If
|
||||
// true is returned the request in the front of the queue can be
|
||||
// sent to the server.
|
||||
bool prepare_next()
|
||||
{
|
||||
if (req_info_.empty()) {
|
||||
req_info_.push_back({});
|
||||
if (info_.empty()) {
|
||||
info_.push_back({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push_back({});
|
||||
if (info_.front().sent) {
|
||||
// There is a pending response, we can't modify the front of
|
||||
// the vector.
|
||||
BOOST_ASSERT(info_.front().cmds != 0);
|
||||
if (info_.size() == 1)
|
||||
info_.push_back({});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
// When cmds = 0 there are only commands with push response on
|
||||
// the request and we are not waiting for any response.
|
||||
return info_.front().cmds == 0;
|
||||
}
|
||||
|
||||
// Returns true when the next request can be writen.
|
||||
bool on_cmd(Command)
|
||||
// Returns true when the next request can be written.
|
||||
bool on_cmd(command_info_type)
|
||||
{
|
||||
// TODO: If the response to a discard is received we have to
|
||||
// remove all commands up until multi.
|
||||
|
||||
assert(!req_info_.empty());
|
||||
assert(!commands_.empty());
|
||||
BOOST_ASSERT(!info_.empty());
|
||||
BOOST_ASSERT(!commands_.empty());
|
||||
|
||||
commands_.erase(std::begin(commands_));
|
||||
|
||||
if (--req_info_.front().cmds != 0)
|
||||
if (--info_.front().cmds != 0)
|
||||
return false;
|
||||
|
||||
req_info_.erase(std::begin(req_info_));
|
||||
info_.erase(std::begin(info_));
|
||||
|
||||
return !req_info_.empty();
|
||||
return !info_.empty();
|
||||
}
|
||||
|
||||
// Reads messages asynchronously.
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
// Resolves the address passed in async_run and store the results
|
||||
// in endpoints_.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_reader(
|
||||
Receiver* recv,
|
||||
async_resolve(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::resolve_op<client>{this}, token, resv_.get_executor());
|
||||
}
|
||||
|
||||
// Connects the socket to one of the endpoints in endpoints_ and
|
||||
// stores the successful endpoint in endpoint_.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_connect(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::connect_op<client>{this}, token, write_timer_.get_executor());
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_read_until(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::read_until_op<client>{this}, token, read_timer_.get_executor());
|
||||
}
|
||||
|
||||
// Reads a complete resp3 response from the socket using the
|
||||
// timeout config::read_timeout. On a successful read calls
|
||||
// on_read_ or on_push_ depending on whether the response is a push
|
||||
// or a response to a command.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_read(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::read_op<client>{this}, token, read_timer_.get_executor());
|
||||
}
|
||||
|
||||
// Loops on async_read described above.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
reader(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::reader_op<client, Command>{this}, token, read_timer_.get_executor());
|
||||
}
|
||||
|
||||
// Write with a timeout.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_write(
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(read_op<client, Receiver, Command>{this, recv}, token, socket_);
|
||||
>(detail::write_op<client>{this}, token, write_timer_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_writer(
|
||||
Receiver* recv,
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
writer(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(writer_op<client, Receiver>{this, recv}, token, socket_, timer_);
|
||||
>(detail::writer_op<client>{this}, token, wait_write_timer_);
|
||||
}
|
||||
|
||||
template <
|
||||
class Receiver,
|
||||
class CompletionToken = default_completion_token_type>
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_read_write(
|
||||
Receiver* recv,
|
||||
CompletionToken&& token = default_completion_token_type{})
|
||||
async_init(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(read_write_op<client, Receiver>{this, recv}, token, socket_, timer_);
|
||||
>(detail::init_op<client>{this}, token, write_timer_, resv_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_read_write_check(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::read_write_check_op<client>{this}, token, read_timer_, write_timer_, wait_write_timer_, check_idle_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_ping_after(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::ping_after_op<client, Command>{this}, token, read_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_wait_for_data(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::wait_for_data_op<client>{this}, token, read_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_check_idle(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::check_idle_op<client>{this}, token, check_idle_timer_);
|
||||
}
|
||||
|
||||
void on_reader_exit()
|
||||
{
|
||||
socket_->close();
|
||||
wait_write_timer_.expires_at(std::chrono::steady_clock::now());
|
||||
}
|
||||
|
||||
// Stores information about a request.
|
||||
struct info {
|
||||
// Set to true before calling async_write.
|
||||
bool sent = false;
|
||||
|
||||
// Request size in bytes. After a successful write it is set to
|
||||
// zero.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains. Commands with push
|
||||
// responses are not counted.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Used to resolve the host on async_resolve.
|
||||
boost::asio::ip::tcp::resolver resv_;
|
||||
|
||||
// The tcp socket.
|
||||
std::shared_ptr<AsyncReadWriteStream> socket_;
|
||||
|
||||
// Timer used with async_read.
|
||||
boost::asio::steady_timer read_timer_;
|
||||
|
||||
// Timer used with async_write.
|
||||
boost::asio::steady_timer write_timer_;
|
||||
|
||||
// Timer that is canceled when a new message is added to the output
|
||||
// queue.
|
||||
boost::asio::steady_timer wait_write_timer_;
|
||||
|
||||
// Check idle timer.
|
||||
boost::asio::steady_timer check_idle_timer_;
|
||||
|
||||
// Configuration parameters.
|
||||
config cfg_;
|
||||
|
||||
// Called when a complete message is read.
|
||||
read_handler_type on_read_;
|
||||
|
||||
// Called when a request has been written to the socket.
|
||||
write_handler_type on_write_;
|
||||
|
||||
// Called when a complete push message is received.
|
||||
push_handler_type on_push_;
|
||||
|
||||
// Called by the parser after each new chunk of resp3 data is
|
||||
// processed.
|
||||
resp3_handler_type on_resp3_;
|
||||
|
||||
// Buffer used by the read operations.
|
||||
std::string read_buffer_;
|
||||
|
||||
// Requests payload and its serializer.
|
||||
std::string requests_;
|
||||
serializer<std::string> sr_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::vector<command_info_type> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::vector<info> info_;
|
||||
|
||||
// Last time we received data.
|
||||
time_point_type last_data_;
|
||||
|
||||
// Used by the read_op.
|
||||
resp3::type type_;
|
||||
|
||||
// Used by the read_op.
|
||||
command_info_type cmd_info_;
|
||||
|
||||
// See async_connect.
|
||||
boost::asio::ip::tcp::endpoint endpoint_;
|
||||
|
||||
// See async_resolve.
|
||||
boost::asio::ip::tcp::resolver::results_type endpoints_;
|
||||
|
||||
// Host and port passed to async_run.
|
||||
boost::string_view host_;
|
||||
boost::string_view port_;
|
||||
};
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_GENERIC_CLIENT_HPP
|
||||
|
||||
@@ -1,205 +1,554 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_GENERIC_CLIENT_OPS_HPP
|
||||
#define AEDIS_GENERIC_CLIENT_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/experimental/parallel_group.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/generic/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
namespace detail {
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
template <class Client, class Receiver>
|
||||
struct run_op {
|
||||
template <class Client, class Command>
|
||||
struct ping_after_op {
|
||||
Client* cli;
|
||||
Receiver* recv_;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
void
|
||||
operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) {
|
||||
yield cli->socket_.async_connect(cli->endpoint_, std::move(self));
|
||||
reenter (coro)
|
||||
{
|
||||
BOOST_ASSERT((cli->cfg_.idle_timeout / 2) != std::chrono::seconds{0});
|
||||
cli->read_timer_.expires_after(cli->cfg_.idle_timeout / 2);
|
||||
yield cli->read_timer_.async_wait(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield cli->async_read_write(recv_, std::move(self));
|
||||
// The timer fired, send the ping.
|
||||
cli->send(Command::ping);
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct read_until_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Waits for incomming data.
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
*cli->socket_,
|
||||
boost::asio::dynamic_buffer(cli->read_buffer_, cli->cfg_.max_read_size),
|
||||
"\r\n",
|
||||
std::move(self));
|
||||
|
||||
// Cancels the async_ping_after.
|
||||
cli->read_timer_.cancel();
|
||||
self.complete(ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client, class Receiver>
|
||||
struct read_write_op {
|
||||
template <class Client>
|
||||
struct wait_for_data_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
)
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro) {
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return cli->async_read_until(token);},
|
||||
[this](auto token) { return cli->async_ping_after(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_all(),
|
||||
std::move(self));
|
||||
|
||||
// The order of completion is not important.
|
||||
self.complete(ec1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct check_idle_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for(;;)
|
||||
{
|
||||
cli->check_idle_timer_.expires_after(cli->cfg_.idle_timeout);
|
||||
yield cli->check_idle_timer_.async_wait(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
if (cli->last_data_ + cli->cfg_.idle_timeout < now) {
|
||||
cli->on_reader_exit();
|
||||
self.complete(error::idle_timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
cli->last_data_ = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct resolve_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::resolver::results_type res = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
cli->resv_.async_resolve(cli->host_.data(), cli->port_.data(), std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
cli->endpoints_ = res;
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct connect_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, boost::asio::ip::tcp::endpoint const& ep = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield
|
||||
boost::asio::async_connect(
|
||||
*cli->socket_,
|
||||
cli->endpoints_,
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
cli->endpoint_ = ep;
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct init_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Tries to resolve with a timeout. We can use the writer
|
||||
// timer here as there is no ongoing write operation.
|
||||
cli->write_timer_.expires_after(cli->cfg_.resolve_timeout);
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return cli->async_writer(recv, token);},
|
||||
[this](auto token) { return cli->async_reader(recv, token);}
|
||||
[this](auto token) { return cli->async_resolve(token);},
|
||||
[this](auto token) { return cli->write_timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1);
|
||||
return;
|
||||
}
|
||||
|
||||
cli->on_resolve();
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
self.complete(generic::error::resolve_timeout);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
// Tries a connection with a timeout. We can use the writer
|
||||
// timer here as there is no ongoing write operation.
|
||||
cli->write_timer_.expires_after(cli->cfg_.connect_timeout);
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return cli->async_connect(token);},
|
||||
[this](auto token) { return cli->write_timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1);
|
||||
return;
|
||||
}
|
||||
|
||||
cli->on_connect();
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
self.complete(generic::error::connect_timeout);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct read_write_check_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 3> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, boost::system::error_code ec2 = {}
|
||||
, boost::system::error_code ec3 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
// Starts the reader and writer ops.
|
||||
cli->wait_write_timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return cli->writer(token);},
|
||||
[this](auto token) { return cli->reader(token);},
|
||||
[this](auto token) { return cli->async_check_idle(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one_error(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0: self.complete(ec1); break;
|
||||
case 1: self.complete(ec2); break;
|
||||
default: assert(false);
|
||||
case 0:
|
||||
{
|
||||
BOOST_ASSERT(ec1);
|
||||
self.complete(ec1);
|
||||
} break;
|
||||
case 1:
|
||||
{
|
||||
BOOST_ASSERT(ec2);
|
||||
self.complete(ec2);
|
||||
} break;
|
||||
case 2:
|
||||
{
|
||||
BOOST_ASSERT(ec3);
|
||||
self.complete(ec3);
|
||||
} break;
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Consider limiting the size of the pipelines by spliting that last
|
||||
// one in two if needed.
|
||||
template <class Client, class Receiver>
|
||||
struct writer_op {
|
||||
template <class Client>
|
||||
struct run_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
yield cli->async_init(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield cli->async_read_write_check(std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct write_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
std::size_t size;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, std::size_t n = 0
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro) for (;;) {
|
||||
|
||||
boost::ignore_unused(n);
|
||||
|
||||
assert(!cli->req_info_.empty());
|
||||
assert(cli->req_info_.front().size != 0);
|
||||
assert(!cli->requests_.empty());
|
||||
reenter (coro)
|
||||
{
|
||||
BOOST_ASSERT(!cli->info_.empty());
|
||||
BOOST_ASSERT(cli->info_.front().size != 0);
|
||||
BOOST_ASSERT(!cli->requests_.empty());
|
||||
|
||||
cli->write_timer_.expires_after(cli->cfg_.write_timeout);
|
||||
cli->info_.front().sent = true;
|
||||
yield
|
||||
boost::asio::async_write(
|
||||
cli->socket_,
|
||||
boost::asio::buffer(cli->requests_.data(), cli->req_info_.front().size),
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return boost::asio::async_write(*cli->socket_, boost::asio::buffer(cli->requests_.data(), cli->info_.front().size), token);},
|
||||
[this](auto token) { return cli->write_timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
self.complete(generic::error::write_timeout);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
BOOST_ASSERT(!cli->info_.empty());
|
||||
BOOST_ASSERT(cli->info_.front().size != 0);
|
||||
BOOST_ASSERT(!cli->requests_.empty());
|
||||
BOOST_ASSERT(n == cli->info_.front().size);
|
||||
|
||||
cli->requests_.erase(0, n);
|
||||
cli->info_.front().size = 0;
|
||||
if (cli->info_.front().cmds == 0)
|
||||
cli->info_.erase(std::begin(cli->info_));
|
||||
|
||||
cli->on_write_(n);
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct writer_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()(Self& self , boost::system::error_code ec = {})
|
||||
{
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
yield cli->async_write(std::move(self));
|
||||
if (ec) {
|
||||
cli->socket_.close();
|
||||
cli->socket_->close();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
size = cli->req_info_.front().size;
|
||||
yield cli->wait_write_timer_.async_wait(std::move(self));
|
||||
|
||||
cli->requests_.erase(0, cli->req_info_.front().size);
|
||||
cli->req_info_.front().size = 0;
|
||||
|
||||
if (cli->req_info_.front().cmds == 0)
|
||||
cli->req_info_.erase(std::begin(cli->req_info_));
|
||||
|
||||
recv->on_write(size);
|
||||
|
||||
yield cli->timer_.async_wait(std::move(self));
|
||||
|
||||
if (cli->stop_writer_) {
|
||||
self.complete(ec);
|
||||
if (!cli->socket_->is_open()) {
|
||||
self.complete(error::write_stop_requested);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client, class Receiver, class Command>
|
||||
template <class Client>
|
||||
struct read_op {
|
||||
Client* cli;
|
||||
Receiver* recv;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
// Consider moving this variables to the client to spare some
|
||||
// memory in the competion handler.
|
||||
resp3::type t = resp3::type::invalid;
|
||||
Command cmd = Command::invalid;
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, std::array<std::size_t, 2> order = {}
|
||||
, boost::system::error_code ec1 = {}
|
||||
, std::size_t n = 0
|
||||
, boost::system::error_code ec2 = {})
|
||||
{
|
||||
reenter (coro)
|
||||
{
|
||||
cli->read_timer_.expires_after(cli->cfg_.read_timeout);
|
||||
|
||||
yield
|
||||
boost::asio::experimental::make_parallel_group(
|
||||
[this](auto token) { return resp3::async_read(*cli->socket_, boost::asio::dynamic_buffer(cli->read_buffer_, cli->cfg_.max_read_size), [cli_ = cli](resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable {cli_->on_resp3_(cli_->cmd_info_.first, nd, ec);}, token);},
|
||||
[this](auto token) { return cli->read_timer_.async_wait(token);}
|
||||
).async_wait(
|
||||
boost::asio::experimental::wait_for_one(),
|
||||
std::move(self));
|
||||
|
||||
switch (order[0]) {
|
||||
case 0:
|
||||
{
|
||||
if (ec1) {
|
||||
self.complete(ec1);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
if (!ec2) {
|
||||
self.complete(generic::error::read_timeout);
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
|
||||
if (cli->type_ == resp3::type::push) {
|
||||
cli->on_push_(n);
|
||||
} else {
|
||||
if (cli->on_cmd(cli->cmd_info_))
|
||||
cli->wait_write_timer_.cancel_one();
|
||||
|
||||
cli->on_read_(cli->cmd_info_.first, n);
|
||||
}
|
||||
|
||||
self.complete({});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client, class Command>
|
||||
struct reader_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro) for (;;) {
|
||||
|
||||
boost::ignore_unused(n);
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
if (cli->read_buffer_.empty()) {
|
||||
yield
|
||||
boost::asio::async_read_until(
|
||||
cli->socket_,
|
||||
boost::asio::dynamic_buffer(cli->read_buffer_),
|
||||
"\r\n",
|
||||
std::move(self));
|
||||
|
||||
yield cli->async_wait_for_data(std::move(self));
|
||||
if (ec) {
|
||||
cli->stop_writer_ = true;
|
||||
cli->on_reader_exit();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
assert(!cli->read_buffer_.empty());
|
||||
t = resp3::detail::to_type(cli->read_buffer_.front());
|
||||
cmd = Command::invalid;
|
||||
if (t != resp3::type::push) {
|
||||
assert(!cli->commands_.empty());
|
||||
cmd = cli->commands_.front();
|
||||
BOOST_ASSERT(!cli->read_buffer_.empty());
|
||||
cli->type_ = resp3::to_type(cli->read_buffer_.front());
|
||||
cli->cmd_info_ = std::make_pair(Command::invalid, 0);
|
||||
if (cli->type_ != resp3::type::push) {
|
||||
BOOST_ASSERT(!cli->commands_.empty());
|
||||
cli->cmd_info_ = cli->commands_.front();
|
||||
}
|
||||
|
||||
yield
|
||||
resp3::async_read(
|
||||
cli->socket_,
|
||||
boost::asio::dynamic_buffer(cli->read_buffer_),
|
||||
[p = recv, c = cmd](resp3::node<boost::string_view> const& nd, boost::system::error_code& ec) mutable {p->on_resp3(c, nd, ec);},
|
||||
std::move(self));
|
||||
cli->last_data_ = std::chrono::steady_clock::now();
|
||||
|
||||
yield cli->async_read(std::move(self));
|
||||
if (ec) {
|
||||
cli->stop_writer_ = true;
|
||||
cli->on_reader_exit();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == resp3::type::push) {
|
||||
recv->on_push();
|
||||
} else {
|
||||
if (cli->on_cmd(cmd))
|
||||
cli->timer_.cancel_one();
|
||||
|
||||
recv->on_read(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // generic
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_GENERIC_CLIENT_OPS_HPP
|
||||
|
||||
54
aedis/generic/error.hpp
Normal file
54
aedis/generic/error.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef AEDIS_GENERIC_ERROR_HPP
|
||||
#define AEDIS_GENERIC_ERROR_HPP
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
|
||||
/** \brief Generic errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Represents the timeout of the resolve operation.
|
||||
resolve_timeout = 1,
|
||||
|
||||
/// Represents the timeout of the connect operation.
|
||||
connect_timeout,
|
||||
|
||||
/// Represents the timeout of the read operation.
|
||||
read_timeout,
|
||||
|
||||
/// Represents the timeout of the write operation.
|
||||
write_timeout,
|
||||
|
||||
/// Idle timeout.
|
||||
idle_timeout,
|
||||
|
||||
/// Write stop requested.
|
||||
write_stop_requested,
|
||||
};
|
||||
|
||||
/** \brief Creates a error_code object from an error.
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::generic::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_GENERIC_ERROR_HPP
|
||||
48
aedis/generic/impl/error.ipp
Normal file
48
aedis/generic/impl/error.ipp
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/generic/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{
|
||||
return "aedis.generic";
|
||||
}
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::resolve_timeout: return "Resolve operation timeout.";
|
||||
case error::connect_timeout: return "Connect operation timeout.";
|
||||
case error::read_timeout: return "Read operation timeout.";
|
||||
case error::write_timeout: return "Write operation timeout.";
|
||||
case error::idle_timeout: return "Idle timeout.";
|
||||
case error::write_stop_requested: return "Write stop requested.";
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
boost::system::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
boost::system::error_code make_error_code(error e)
|
||||
{
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
@@ -1,26 +1,32 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_GENERIC_SERIALIZER_HPP
|
||||
#define AEDIS_GENERIC_SERIALIZER_HPP
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <aedis/resp3/compose.hpp>
|
||||
|
||||
// NOTE: Consider detecting tuples in the type in the parameter pack
|
||||
// to calculate the header size correctly.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
|
||||
/** @brief Creates a Redis request from user data.
|
||||
/** @brief Creates Redis requests from user data.
|
||||
* \ingroup any
|
||||
*
|
||||
* A request is composed of one or more redis commands and is
|
||||
* referred to in the redis documentation as a pipeline, see
|
||||
* https://redis.io/topics/pipelining.
|
||||
*
|
||||
* For example
|
||||
* Example
|
||||
*
|
||||
* @code
|
||||
* std::string request;
|
||||
@@ -34,17 +40,10 @@ namespace generic {
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type e.g \c std::string.
|
||||
* \tparam Command The command to serialize.
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_string, which must be made available over ADL.
|
||||
* to_bulk, which must be made available over ADL.
|
||||
*/
|
||||
|
||||
// Consider detecting tuples in the type in the parameter pack to
|
||||
// calculate the header size correctly.
|
||||
//
|
||||
// NOTE: For some commands like hset it would be a good idea to assert
|
||||
// the value type is a pair.
|
||||
template <class Storage>
|
||||
class serializer {
|
||||
private:
|
||||
@@ -53,8 +52,8 @@ private:
|
||||
public:
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param storage Object where the serialized request will be
|
||||
* stored.
|
||||
* \param storage The underlying storage object i.e. where the
|
||||
* request is to be stored.
|
||||
*/
|
||||
serializer(Storage& storage) : request_(&storage) {}
|
||||
|
||||
@@ -68,21 +67,21 @@ public:
|
||||
* sr.push(command::set, "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with payload "some string" and an
|
||||
* will add the \c set command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The redis command.
|
||||
* \param args The arguments of the Redis command.
|
||||
*
|
||||
* \param cmd The command e.g redis or sentinel command.
|
||||
* \param args Command arguments.
|
||||
*/
|
||||
template <class Command, class... Ts>
|
||||
void push(Command cmd, Ts const&... args)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
using boost::hana::make_tuple;
|
||||
using resp3::type;
|
||||
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
resp3::add_header(*request_, 1 + pack_size);
|
||||
resp3::add_header(*request_, type::array, 1 + pack_size);
|
||||
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, make_tuple(args...));
|
||||
@@ -90,9 +89,10 @@ public:
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that require a key. For example
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* \code{.cpp}
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
@@ -101,10 +101,10 @@ public:
|
||||
*
|
||||
* request req;
|
||||
* req.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* \endcode
|
||||
* @endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param key The key the Redis command refers to.
|
||||
* \param cmd The command e.g. Redis or Sentinel command.
|
||||
* \param key The command key.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
@@ -112,13 +112,14 @@ public:
|
||||
void push_range2(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(*request_, 2 + size * distance);
|
||||
resp3::add_header(*request_, type::array, 2 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, key);
|
||||
|
||||
@@ -128,8 +129,8 @@ public:
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that don't have a key. For
|
||||
* example
|
||||
* This overload is useful for commands that have a dynamic number
|
||||
* of arguments and don't have a key. For example
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
@@ -147,13 +148,14 @@ public:
|
||||
void push_range2(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
using resp3::type;
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto constexpr size = resp3::bulk_counter<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
resp3::add_header(*request_, 1 + size * distance);
|
||||
resp3::add_header(*request_, type::array, 1 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
@@ -162,7 +164,7 @@ public:
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Similar to the range version.
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*/
|
||||
template <class Command, class Key, class Range>
|
||||
void push_range(Command cmd, Key const& key, Range const& range)
|
||||
@@ -174,7 +176,7 @@ public:
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Similar to the range version.
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*/
|
||||
template <class Command, class Range>
|
||||
void push_range(Command cmd, Range const& range)
|
||||
@@ -190,11 +192,12 @@ public:
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
serializer<std::basic_string<CharT, Traits, Allocator>>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
auto make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return serializer<std::basic_string<CharT, Traits, Allocator>>(storage);
|
||||
}
|
||||
|
||||
} // generic
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_GENERIC_SERIALIZER_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_REDIS_COMMAND_HPP
|
||||
#define AEDIS_REDIS_COMMAND_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
@@ -16,10 +16,11 @@ namespace redis {
|
||||
/** \brief Redis commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/commands.
|
||||
* The full list of of commands can be found at
|
||||
* https://redis.io/commands.
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
* \remark This list was created with the help of the \c command
|
||||
* command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
@@ -72,7 +73,7 @@ enum class command {
|
||||
decrby,
|
||||
/// https://redis.io/commands/del
|
||||
del,
|
||||
/// https://redis.io/commands/discard (not supported yet)
|
||||
/// https://redis.io/commands/discard
|
||||
discard,
|
||||
/// https://redis.io/commands/dump
|
||||
dump,
|
||||
@@ -434,25 +435,23 @@ enum class command {
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Converts a command to a string
|
||||
/** \brief Converts the command to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
/** \brief Writes the command string to the stream.
|
||||
* \ingroup any
|
||||
* \param os Output stream.
|
||||
* \param c Redis command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
// Checks whether a command has push response.
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_REDIS_COMMAND_HPP
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_COMPOSE_HPP
|
||||
#define AEDIS_RESP3_COMPOSE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -13,22 +13,39 @@
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Adds data to the request.
|
||||
constexpr char separator[] = "\r\n";
|
||||
|
||||
/** @brief Adds a bulk to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* This function is useful in serialization of your own data
|
||||
* structures in a request. For example
|
||||
*
|
||||
* @code
|
||||
* void to_bulk(std::string& to, mystruct const& obj)
|
||||
* {
|
||||
* auto const str = // Convert obj to a string.
|
||||
* resp3::to_bulk(to, str);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* See more in \ref requests-serialization.
|
||||
*/
|
||||
template <class Request>
|
||||
void to_bulk(Request& to, boost::string_view data)
|
||||
{
|
||||
auto const str = std::to_string(data.size());
|
||||
|
||||
to += "$";
|
||||
to += to_code(type::blob_string);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += separator;
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += "\r\n";
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
@@ -80,23 +97,24 @@ struct add_bulk_impl<boost::hana::tuple<Ts...>> {
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Adds a resp3 header to the store to.
|
||||
/** @brief Adds a resp3 header to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* See mystruct.hpp for an example.
|
||||
*/
|
||||
template <class Request>
|
||||
void add_header(Request& to, std::size_t size)
|
||||
void add_header(Request& to, type t, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += "*";
|
||||
to += to_code(t);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += separator;
|
||||
}
|
||||
|
||||
/** @brief Adds a rep3 bulk to the request.
|
||||
* @ingroup any
|
||||
/* Adds a rep3 bulk to the request.
|
||||
*
|
||||
* This function adds \c data as a bulk string to the request \c to.
|
||||
* This function adds \c data as a bulk string to the request \c to.
|
||||
*/
|
||||
template <class Request, class T>
|
||||
void add_bulk(Request& to, T const& data)
|
||||
@@ -117,5 +135,25 @@ struct bulk_counter<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
template <class Request>
|
||||
void add_blob(Request& to, boost::string_view blob)
|
||||
{
|
||||
to.append(std::cbegin(blob), std::cend(blob));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
/** @brief Adds a separator to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* See mystruct.hpp for an example.
|
||||
*/
|
||||
template <class Request>
|
||||
void add_separator(Request& to)
|
||||
{
|
||||
to += separator;
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_COMPOSE_HPP
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
@@ -12,7 +14,8 @@ namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
|
||||
std::size_t
|
||||
parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret;
|
||||
@@ -22,29 +25,6 @@ std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_
|
||||
return ret;
|
||||
}
|
||||
|
||||
type to_type(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
case '=': return type::verbatim_string;
|
||||
case '$': return type::blob_string;
|
||||
case ';': return type::streamed_string_part;
|
||||
case '-': return type::simple_error;
|
||||
case ':': return type::number;
|
||||
case ',': return type::doublean;
|
||||
case '#': return type::boolean;
|
||||
case '(': return type::big_number;
|
||||
case '+': return type::simple_string;
|
||||
case '_': return type::null;
|
||||
case '>': return type::push;
|
||||
case '~': return type::set;
|
||||
case '*': return type::array;
|
||||
case '|': return type::attribute;
|
||||
case '%': return type::map;
|
||||
default: return type::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_PARSER_HPP
|
||||
#define AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
@@ -24,9 +22,6 @@ namespace detail {
|
||||
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
private:
|
||||
@@ -68,7 +63,7 @@ public:
|
||||
switch (bulk_) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
assert(bulk_length_ != 0);
|
||||
BOOST_ASSERT(bulk_length_ != 0);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
@@ -227,3 +222,5 @@ public:
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#ifndef AEDIS_RESP3_READ_OPS_HPP
|
||||
#define AEDIS_RESP3_READ_OPS_HPP
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
@@ -88,7 +87,7 @@ public:
|
||||
}
|
||||
|
||||
n = parser_.bulk_length() + 2;
|
||||
assert(buf_.size() >= n);
|
||||
BOOST_ASSERT(buf_.size() >= n);
|
||||
}
|
||||
|
||||
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
|
||||
@@ -107,44 +106,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class type_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
boost::asio::coroutine coro_;
|
||||
|
||||
public:
|
||||
type_op(AsyncReadStream& stream, DynamicBuffer buf)
|
||||
: stream_ {stream}
|
||||
, buf_ {buf}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
void operator()( Self& self
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
reenter (coro_) {
|
||||
|
||||
boost::ignore_unused(n);
|
||||
if (buf_.size() == 0) {
|
||||
yield boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_READ_OPS_HPP
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_ERROR_HPP
|
||||
#define AEDIS_RESP3_ERROR_HPP
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 parsing errors.
|
||||
/** \brief RESP3 errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
@@ -20,7 +20,7 @@ enum class error
|
||||
/// Invalid RESP3 type.
|
||||
invalid_type = 1,
|
||||
|
||||
/// Can't parse the string as an integer.
|
||||
/// Can't parse the string as a number.
|
||||
not_a_number,
|
||||
|
||||
/// Received less bytes than expected.
|
||||
@@ -29,23 +29,18 @@ enum class error
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth,
|
||||
|
||||
/// Unexpected bool value
|
||||
/// Unexpects bool value.
|
||||
unexpected_bool_value,
|
||||
|
||||
/// Expected field value is empty.
|
||||
empty_field
|
||||
};
|
||||
|
||||
/** \brief Converts an error in an boost::system::error_code object.
|
||||
/** \brief Creates a error_code object from an error.
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
/** \brief todo
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_condition make_error_condition(error e);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -55,3 +50,5 @@ template<>
|
||||
struct is_error_code_enum<::aedis::resp3::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_RESP3_ERROR_HPP
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
@@ -27,7 +27,7 @@ struct error_category_impl : boost::system::error_category {
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
case error::unexpected_bool_value: return "Unexpected bool value.";
|
||||
case error::empty_field: return "Expected field value is empty.";
|
||||
default: assert(false);
|
||||
default: BOOST_ASSERT(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -45,10 +45,5 @@ boost::system::error_code make_error_code(error e)
|
||||
return boost::system::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
boost::system::error_condition make_error_condition(error e)
|
||||
{
|
||||
return boost::system::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
@@ -64,5 +62,54 @@ std::size_t element_multiplicity(type t)
|
||||
}
|
||||
}
|
||||
|
||||
char to_code(type t)
|
||||
{
|
||||
switch (t) {
|
||||
case type::blob_error: return '!';
|
||||
case type::verbatim_string: return '=';
|
||||
case type::blob_string: return '$';
|
||||
case type::streamed_string_part: return ';';
|
||||
case type::simple_error: return '-';
|
||||
case type::number: return ':';
|
||||
case type::doublean: return ',';
|
||||
case type::boolean: return '#';
|
||||
case type::big_number: return '(';
|
||||
case type::simple_string: return '+';
|
||||
case type::null: return '_';
|
||||
case type::push: return '>';
|
||||
case type::set: return '~';
|
||||
case type::array: return '*';
|
||||
case type::attribute: return '|';
|
||||
case type::map: return '%';
|
||||
|
||||
default:
|
||||
BOOST_ASSERT(false);
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
type to_type(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case '!': return type::blob_error;
|
||||
case '=': return type::verbatim_string;
|
||||
case '$': return type::blob_string;
|
||||
case ';': return type::streamed_string_part;
|
||||
case '-': return type::simple_error;
|
||||
case ':': return type::number;
|
||||
case ',': return type::doublean;
|
||||
case '#': return type::boolean;
|
||||
case '(': return type::big_number;
|
||||
case '+': return type::simple_string;
|
||||
case '_': return type::null;
|
||||
case '>': return type::push;
|
||||
case '~': return type::set;
|
||||
case '*': return type::array;
|
||||
case '|': return type::attribute;
|
||||
case '%': return type::map;
|
||||
default: return type::invalid;
|
||||
}
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_NODE_HPP
|
||||
#define AEDIS_RESP3_NODE_HPP
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
@@ -21,15 +21,8 @@ namespace resp3 {
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*
|
||||
* The node class represent one element in the response tree. The string type
|
||||
* is a template give more flexibility, for example
|
||||
*
|
||||
* @li @c boost::string_view
|
||||
* @li @c std::string
|
||||
* @li @c boost::static_string
|
||||
*
|
||||
* \remark Any Redis response can be received in an array of nodes, for
|
||||
* example \c std::vector<node<std::string>>.
|
||||
* \remark Any Redis response can be received in an array of nodes,
|
||||
* for example \c std::vector<node<std::string>>.
|
||||
*/
|
||||
template <class String>
|
||||
struct node {
|
||||
@@ -42,7 +35,7 @@ struct node {
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
/// The actual data. For aggregate types this is always empty.
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value;
|
||||
};
|
||||
|
||||
@@ -79,7 +72,7 @@ bool operator==(node<String> const& a, node<String> const& b)
|
||||
&& a.value == b.value;
|
||||
};
|
||||
|
||||
/** \brief Writes the node to the stream.
|
||||
/** \brief Writes the node string to the stream.
|
||||
* \ingroup any
|
||||
*
|
||||
* NOTE: Binary data is not converted to text.
|
||||
@@ -93,3 +86,5 @@ std::ostream& operator<<(std::ostream& os, node<String> const& o)
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_NODE_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_READ_HPP
|
||||
#define AEDIS_RESP3_READ_HPP
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
@@ -19,18 +19,35 @@
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Read the response to a command sychronously.
|
||||
* \ingroup functions
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup any
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
* This function reads a complete response to a command or a
|
||||
* server push synchronously. For example
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param ec Error if any.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
* @code
|
||||
* int resp;
|
||||
* std::string buffer;
|
||||
* resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* For a complete example see examples/intro_sync.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::read_until and @c asio::read functions, and is known as a @a
|
||||
* composed @a operation. Furthermore, the implementation may read
|
||||
* additional bytes from the stream that lie past the end of the
|
||||
* message being read. These additional bytes are stored in the
|
||||
* dynamic buffer, which must be preserved for subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buf Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter, see more on \ref low-level-responses.
|
||||
* \param ec If an error occurs, it will be assigned to this paramter.
|
||||
* \returns The number of bytes that have been consumed from the dynamic buffer.
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class SyncReadStream,
|
||||
@@ -86,17 +103,10 @@ read(
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/** \brief Reads the reponse to a command.
|
||||
* \ingroup functions
|
||||
/** \brief Reads a complete response to a command sychronously.
|
||||
* \ingroup any
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buf Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \returns The number of bytes that have been consumed from the
|
||||
* auxiliary buffer.
|
||||
* Same as the error_code overload but throws on error.
|
||||
*/
|
||||
template<
|
||||
class SyncReadStream,
|
||||
@@ -117,25 +127,43 @@ read(
|
||||
return n;
|
||||
}
|
||||
|
||||
/** @brief Reads the response to a Redis command asynchronously.
|
||||
* \ingroup functions
|
||||
/** @brief Reads a complete response to a Redis command asynchronously.
|
||||
* \ingroup any
|
||||
*
|
||||
* This function has to be called once for each command in the
|
||||
* request until the whole request has been read.
|
||||
*
|
||||
* The completion handler must have the following signature.
|
||||
* This function reads a complete response to a command or a
|
||||
* server push asynchronously. For example
|
||||
*
|
||||
* @code
|
||||
* void(boost::system::error_code, std::size_t)
|
||||
* std::string buffer;
|
||||
* std::set<std::string> resp;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
* @endcode
|
||||
*
|
||||
* The second argumet to the completion handler is the number of
|
||||
* bytes that have been consumed in the read operation.
|
||||
* For a complete example see examples/transaction.cpp. This function
|
||||
* is implemented in terms of one or more calls to @c
|
||||
* asio::async_read_until and @c asio::async_read functions, and is
|
||||
* known as a @a composed @a operation. Furthermore, the
|
||||
* implementation may read additional bytes from the stream that lie
|
||||
* past the end of the message being read. These additional bytes are
|
||||
* stored in the dynamic buffer, which must be preserved for
|
||||
* subsequent reads.
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param adapter The response adapter, see adapt.
|
||||
* \param stream The stream from which to read e.g. a tcp socket.
|
||||
* \param buffer Dynamic buffer (version 2).
|
||||
* \param adapter The response adapter, see more on \ref low-level-responses.
|
||||
* \param token The completion token.
|
||||
*
|
||||
* The completion handler will receive as a parameter the total
|
||||
* number of bytes transferred from the stream and must have the
|
||||
* following signature
|
||||
*
|
||||
* @code
|
||||
* void(boost::system::error_code, std::size_t);
|
||||
* @endcode
|
||||
*
|
||||
* \remark This function calls buf.consume() in each chunk of data
|
||||
* after it has been passed to the adapter. Users must not consume
|
||||
* the bytes after it returns.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
@@ -158,39 +186,9 @@ auto async_read(
|
||||
stream);
|
||||
}
|
||||
|
||||
/** \brief Reads the RESP3 type of the next incomming.
|
||||
* \ingroup functions
|
||||
*
|
||||
* This function won't consume any data from the buffer. The
|
||||
* completion handler must have the following signature.
|
||||
*
|
||||
* @code
|
||||
void(boost::system::error_code, type)
|
||||
* @endcode
|
||||
*
|
||||
* \param stream The stream from which to read.
|
||||
* \param buffer Auxiliary read buffer, usually a `std::string`.
|
||||
* \param token The completion token.
|
||||
*/
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
class CompletionToken =
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read_type(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
CompletionToken&& token =
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, type)
|
||||
>(detail::type_op<AsyncReadStream, DynamicBuffer> {stream, buffer}, token, stream);
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
#endif // AEDIS_RESP3_READ_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 - 2021 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_RESP3_TYPE_HPP
|
||||
#define AEDIS_RESP3_TYPE_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
@@ -14,10 +14,10 @@
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 types
|
||||
/** \brief RESP3 data types.
|
||||
\ingroup any
|
||||
|
||||
The RESP3 full specification can be found at https://github.com/antirez/RESP3/blob/74adea588783e463c7e84793b325b088fe6edd1c/spec.md
|
||||
The RESP3 specification can be found at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md.
|
||||
*/
|
||||
enum class type
|
||||
{ /// Aggregate
|
||||
@@ -56,33 +56,34 @@ enum class type
|
||||
invalid
|
||||
};
|
||||
|
||||
/** \brief Returns the string representation of the type.
|
||||
/** \brief Converts the data type to a string.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
char const* to_string(type t);
|
||||
|
||||
/** \brief Writes the type to the output stream.
|
||||
* \ingroup operators
|
||||
* \ingroup any
|
||||
* \param os Output stream.
|
||||
* \param t RESP3 type.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, type t);
|
||||
|
||||
/** \brief Returns true if the data type is an aggregate.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
/* Checks whether the data type is an aggregate.
|
||||
*/
|
||||
bool is_aggregate(type t);
|
||||
|
||||
/** @brief Returns the element multilicity.
|
||||
* \ingroup any
|
||||
* \param t RESP3 type.
|
||||
*
|
||||
* For type map and attribute this value is 2, all other types have
|
||||
* 1.
|
||||
*/
|
||||
// For map and attribute data types this function returns 2. All
|
||||
// other types have value 1.
|
||||
std::size_t element_multiplicity(type t);
|
||||
|
||||
// Returns the wire code of a given type.
|
||||
char to_code(type t);
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_TYPE_HPP
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_SENTINEL_COMMAND_HPP
|
||||
#define AEDIS_SENTINEL_COMMAND_HPP
|
||||
|
||||
#include <ostream>
|
||||
|
||||
@@ -15,10 +15,11 @@ namespace sentinel {
|
||||
/** \brief Sentinel commands.
|
||||
* \ingroup any
|
||||
*
|
||||
* For a full list of commands see https://redis.io/topics/sentinel
|
||||
* The full list of Sentinel commands can be found at
|
||||
* https://redis.io/topics/sentinel.
|
||||
*
|
||||
* \remark The list of commands below are read from Redis with the
|
||||
* help of the command \c command.
|
||||
* \remark This list was created with the help of the \c command
|
||||
* command.
|
||||
*/
|
||||
enum class command {
|
||||
/// https://redis.io/commands/acl
|
||||
@@ -53,33 +54,25 @@ enum class command {
|
||||
unsubscribe,
|
||||
/// Unknown/invalid command.
|
||||
invalid,
|
||||
// For internal usege only, users should ignore this.
|
||||
multi,
|
||||
// For internal usege only, users should ignore this.
|
||||
discard,
|
||||
// For internal usege only, users should ignore this.
|
||||
exec,
|
||||
};
|
||||
|
||||
/** \brief Converts a sentinel command to a string
|
||||
/** \brief Converts the command to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param c The command to convert.
|
||||
*/
|
||||
char const* to_string(command c);
|
||||
|
||||
/** \brief Write the text for a sentinel command name to an output stream.
|
||||
* \ingroup operators
|
||||
*
|
||||
/** \brief Writes the command string to the stream.
|
||||
* \ingroup any
|
||||
* \param os Output stream.
|
||||
* \param c Sentinel command
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, command c);
|
||||
|
||||
/** \brief Returns true for sentinel commands with push response.
|
||||
* \ingroup any
|
||||
*/
|
||||
// Checks whether a command has push response.
|
||||
bool has_push_response(command cmd);
|
||||
|
||||
} // sentinel
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_SENTINEL_COMMAND_HPP
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <aedis/sentinel/command.hpp>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
// Include this file in no more than one source file in your application.
|
||||
|
||||
#include <aedis/redis/impl/command.ipp>
|
||||
#include <aedis/sentinel/impl/command.ipp>
|
||||
#include <aedis/resp3/impl/type.ipp>
|
||||
#include <aedis/resp3/detail/impl/parser.ipp>
|
||||
#include <aedis/resp3/impl/error.ipp>
|
||||
#include <aedis/redis/impl/command.ipp>
|
||||
#include <aedis/adapter/impl/error.ipp>
|
||||
#include <aedis/sentinel/impl/command.ipp>
|
||||
#include <aedis/generic/impl/error.ipp>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([Aedis], [0.1.0], [mzimbres@gmail.com])
|
||||
AC_INIT([Aedis], [0.1.2], [mzimbres@gmail.com])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
#AC_CONFIG_SRCDIR([src/aedis.cpp])
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
|
||||
132
examples/aggregates_high_level.cpp
Normal file
132
examples/aggregates_high_level.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "print.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
|
||||
// Response types we use in this example.
|
||||
using T0 = std::vector<node<std::string>>;
|
||||
using T1 = std::set<std::string>;
|
||||
using T2 = std::map<std::string, std::string>;
|
||||
|
||||
// Some containers we will store in Redis as example.
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
struct receiver {
|
||||
public:
|
||||
receiver(client_type& db)
|
||||
: adapter0_{adapt(resp0_)}
|
||||
, adapter1_{adapt(resp1_)}
|
||||
, adapter2_{adapt(resp2_)}
|
||||
, db_{&db} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::lrange: adapter0_(nd, ec); break;
|
||||
case command::smembers: adapter1_(nd, ec); break;
|
||||
case command::hgetall: adapter2_(nd, ec); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd, std::size_t n)
|
||||
{
|
||||
std::cout << "on_read: " << cmd << ", " << n << "\n";
|
||||
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
db_->send_range(command::rpush, "rpush-key", vec);
|
||||
db_->send_range(command::sadd, "sadd-key", set);
|
||||
db_->send_range(command::hset, "hset-key", map);
|
||||
} break;
|
||||
|
||||
case command::rpush:
|
||||
db_->send(command::lrange, "rpush-key", 0, -1);
|
||||
break;
|
||||
|
||||
case command::sadd:
|
||||
db_->send(command::smembers, "sadd-key");
|
||||
break;
|
||||
|
||||
case command::hset:
|
||||
db_->send(command::hgetall, "hset-key");
|
||||
db_->send(command::quit);
|
||||
break;
|
||||
|
||||
case command::lrange:
|
||||
print_and_clear_aggregate(resp0_);
|
||||
break;
|
||||
|
||||
case command::smembers:
|
||||
print_and_clear(resp1_);
|
||||
break;
|
||||
|
||||
case command::hgetall:
|
||||
print_and_clear(resp2_);
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "on_write: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push(std::size_t n) { }
|
||||
|
||||
private:
|
||||
T0 resp0_;
|
||||
T1 resp1_;
|
||||
T2 resp2_;
|
||||
adapter_t<T0> adapter0_;
|
||||
adapter_t<T1> adapter1_;
|
||||
adapter_t<T2> adapter2_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db.set_receiver(recv);
|
||||
|
||||
db.async_run("127.0.0.1", "6379",
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
@@ -18,13 +17,14 @@
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push()
|
||||
void on_push(std::size_t)
|
||||
{
|
||||
for (auto& session: sessions_)
|
||||
session->deliver(resp_.at(3).value);
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
adapter_t<response_type> adapter_;
|
||||
std::shared_ptr<client_type> db_;
|
||||
std::vector<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
@@ -105,10 +105,9 @@ int main()
|
||||
|
||||
auto db = std::make_shared<client_type>(ioc.get_executor());
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db->set_receiver(recv);
|
||||
|
||||
db->async_run(
|
||||
*recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
db->async_run("127.0.0.1", "6379",
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -17,12 +16,10 @@
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
@@ -42,14 +39,15 @@ net::awaitable<void> example()
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
auto adapter = [](node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
{
|
||||
std::cout << nd << std::endl;
|
||||
};
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
co_await resp3::async_read(socket, dbuffer); // hello
|
||||
co_await resp3::async_read(socket, dbuffer, adapter);
|
||||
co_await resp3::async_read(socket, dbuffer); // quit
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -20,18 +19,19 @@
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::user_session;
|
||||
using aedis::user_session_base;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
class myreceiver {
|
||||
class receiver {
|
||||
public:
|
||||
myreceiver()
|
||||
receiver(std::shared_ptr<client_type> db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{db}
|
||||
{}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
@@ -39,19 +39,16 @@ public:
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
void on_read(command cmd)
|
||||
void on_read(command cmd, std::size_t n)
|
||||
{
|
||||
std::cout << "on_read: " << cmd << " " << n << std::endl;
|
||||
|
||||
switch (cmd) {
|
||||
case command::ping:
|
||||
sessions_.front()->deliver(resp_.front().value);
|
||||
sessions_.pop();
|
||||
if (resp_.front().value != "PONG") {
|
||||
sessions_.front()->deliver(resp_.front().value);
|
||||
sessions_.pop();
|
||||
}
|
||||
break;
|
||||
|
||||
case command::incr:
|
||||
@@ -64,20 +61,44 @@ public:
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push(std::size_t n) { }
|
||||
|
||||
void add_user_session(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push(session); }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
adapter_t<response_type> adapter_;
|
||||
std::shared_ptr<client_type> db_;
|
||||
std::queue<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
|
||||
net::awaitable<void>
|
||||
run_with_reconnect(std::shared_ptr<client_type> db)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
boost::asio::steady_timer timer{ex};
|
||||
|
||||
for (boost::system::error_code ec;;) {
|
||||
co_await db->async_run("127.0.0.1", "6379",
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
timer.expires_after(std::chrono::seconds{2});
|
||||
co_await timer.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<net::ip::tcp::acceptor> acc,
|
||||
std::shared_ptr<client_type> db,
|
||||
std::shared_ptr<myreceiver> recv)
|
||||
std::shared_ptr<receiver> recv)
|
||||
{
|
||||
for (;;) {
|
||||
auto socket = co_await acc->async_accept(net::use_awaitable);
|
||||
@@ -85,9 +106,11 @@ listener(
|
||||
|
||||
auto on_user_msg = [db, recv, session](std::string const& msg)
|
||||
{
|
||||
db->send(command::ping, msg);
|
||||
db->send(command::incr, "echo-counter");
|
||||
recv->add_user_session(session);
|
||||
if (!msg.empty()) {
|
||||
db->send(command::ping, msg);
|
||||
db->send(command::incr, "echo-counter");
|
||||
recv->add_user_session(session);
|
||||
}
|
||||
};
|
||||
|
||||
session->start(on_user_msg);
|
||||
@@ -100,12 +123,9 @@ int main()
|
||||
net::io_context ioc;
|
||||
|
||||
auto db = std::make_shared<client_type>(ioc.get_executor());
|
||||
auto recv = std::make_shared<myreceiver>();
|
||||
|
||||
db->async_run(
|
||||
*recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db->set_receiver(recv);
|
||||
co_spawn(ioc, run_with_reconnect(db), net::detached);
|
||||
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
|
||||
@@ -1,110 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::redis::command;
|
||||
using client_type = aedis::generic::client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
// Prints aggregates that don't contain any nested aggregates.
|
||||
void print_aggregate(response_type const& v)
|
||||
{
|
||||
auto const m = element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
struct receiver {
|
||||
public:
|
||||
receiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Sends the stl containers.
|
||||
db_->send_range(command::hset, "hset-key", map);
|
||||
db_->send_range(command::rpush, "rpush-key", vec);
|
||||
db_->send_range(command::sadd, "sadd-key", set);
|
||||
|
||||
// Retrieves the containers.
|
||||
db_->send(command::hgetall, "hset-key");
|
||||
db_->send(command::lrange, "rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "sadd-key");
|
||||
db_->send(command::quit);
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
case command::smembers:
|
||||
case command::hgetall:
|
||||
print_aggregate(resp_);
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
receiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
// Arbitrary struct to de/serialize.
|
||||
struct mystruct {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace adapter = aedis::adapter;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapters_tuple_t;
|
||||
using aedis::adapter::make_adapters_tuple;
|
||||
using aedis::adapter::get;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using responses_tuple_type =
|
||||
std::tuple<
|
||||
boost::optional<mystruct>,
|
||||
std::list<mystruct>,
|
||||
std::set<mystruct>,
|
||||
std::map<std::string, mystruct>
|
||||
>;
|
||||
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, mystruct const& obj)
|
||||
{
|
||||
os << "a: " << obj.a << ", b: " << obj.b;
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(mystruct const& a, mystruct const& b)
|
||||
{
|
||||
return std::tie(a.a, a.b) < std::tie(b.a, b.b);
|
||||
}
|
||||
|
||||
// Dumy serialization.
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
aedis::resp3::to_bulk(to, "Dummy serializaiton string.");
|
||||
}
|
||||
|
||||
// Dummy deserialization.
|
||||
void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code& ec)
|
||||
{
|
||||
obj.a = 1;
|
||||
obj.b = 2;
|
||||
}
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
receiver(client_type& db)
|
||||
: adapters_(make_adapters_tuple(resps_))
|
||||
, db_{&db} {}
|
||||
|
||||
void
|
||||
on_resp3(
|
||||
command cmd,
|
||||
node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::get: adapter::get<boost::optional<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::lrange: adapter::get<std::list<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::smembers: adapter::get<std::set<mystruct>>(adapters_)(nd, ec); break;
|
||||
case command::hgetall: adapter::get<std::map<std::string, mystruct>>(adapters_)(nd, ec); break;
|
||||
default:; // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
std::cout << cmd << "\n";
|
||||
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
mystruct var{1, 2};
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", {1, 2}}
|
||||
, {"key2", {3, 4}}
|
||||
, {"key3", {5, 6}}};
|
||||
|
||||
std::vector<mystruct> vec
|
||||
{{1, 2}, {3, 4}, {5, 6}};
|
||||
|
||||
std::set<std::string> set
|
||||
{{1, 2}, {3, 4}, {5, 6}};
|
||||
|
||||
// Sends
|
||||
db_->send(command::set, "serialization-var-key", var, "EX", "2");
|
||||
db_->send_range(command::hset, "serialization-hset-key", map);
|
||||
db_->send_range(command::rpush, "serialization-rpush-key", vec);
|
||||
db_->send_range(command::sadd, "serialization-sadd-key", set);
|
||||
|
||||
// Retrieves
|
||||
db_->send(command::get, "serialization-var-key");
|
||||
db_->send(command::hgetall, "serialization-hset-key");
|
||||
db_->send(command::lrange, "serialization-rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "serialization-sadd-key");
|
||||
} break;
|
||||
|
||||
case command::get:
|
||||
{
|
||||
if (std::get<boost::optional<mystruct>>(resps_).has_value()) {
|
||||
std::cout << std::get<boost::optional<mystruct>>(resps_).value() << "\n\n";
|
||||
std::get<boost::optional<mystruct>>(resps_).reset();
|
||||
} else {
|
||||
std::cout << "Expired." << "\n";
|
||||
}
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
for (auto const& e: std::get<std::list<mystruct>>(resps_))
|
||||
std::cout << e << "\n";
|
||||
std::cout << "\n";
|
||||
std::get<std::list<mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
case command::smembers:
|
||||
for (auto const& e: std::get<std::set<mystruct>>(resps_))
|
||||
std::cout << e << "\n";
|
||||
std::cout << "\n";
|
||||
std::get<std::set<mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
case command::hgetall:
|
||||
for (auto const& e: std::get<std::map<std::string, mystruct>>(resps_))
|
||||
std::cout << e.first << ", " << e.second << std::endl;
|
||||
std::cout << "\n";
|
||||
std::get<std::map<std::string, mystruct>>(resps_).clear();
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
net::steady_timer tm{ioc, std::chrono::seconds{3}};
|
||||
|
||||
tm.async_wait([&db](auto ec){
|
||||
db.send(command::get, "serialization-var-key");
|
||||
db.send(command::quit);
|
||||
});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace adapter = aedis::adapter;
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapters_tuple_t;
|
||||
using aedis::adapter::make_adapters_tuple;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using responses_tuple_type =
|
||||
std::tuple<
|
||||
std::list<int>,
|
||||
boost::optional<std::set<std::string>>,
|
||||
std::vector<node<std::string>>
|
||||
>;
|
||||
using adapters_tuple_type = adapters_tuple_t<responses_tuple_type>;
|
||||
|
||||
template <class Container>
|
||||
void print_and_clear(Container& cont)
|
||||
{
|
||||
std::cout << "\n";
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
cont.clear();
|
||||
}
|
||||
|
||||
class myreceiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
: adapters_(make_adapters_tuple(resps_))
|
||||
, db_{&db} {}
|
||||
|
||||
void
|
||||
on_resp3(
|
||||
command cmd,
|
||||
node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::lrange: adapter::get<std::list<int>>(adapters_)(nd, ec); break;
|
||||
case command::smembers: adapter::get<boost::optional<std::set<std::string>>>(adapters_)(nd, ec); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
{
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<int> vec
|
||||
{1, 2, 3, 4, 5, 6};
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Sends the stl containers.
|
||||
db_->send_range(command::hset, "hset-key", map);
|
||||
db_->send_range(command::rpush, "rpush-key", vec);
|
||||
db_->send_range(command::sadd, "sadd-key", set);
|
||||
|
||||
//_ Retrieves the containers.
|
||||
db_->send(command::hgetall, "hset-key");
|
||||
db_->send(command::lrange, "rpush-key", 0, -1);
|
||||
db_->send(command::smembers, "sadd-key");
|
||||
db_->send(command::quit);
|
||||
} break;
|
||||
|
||||
case command::lrange:
|
||||
print_and_clear(std::get<std::list<int>>(resps_));
|
||||
break;
|
||||
|
||||
case command::smembers:
|
||||
print_and_clear(std::get<boost::optional<std::set<std::string>>>(resps_).value());
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
|
||||
private:
|
||||
responses_tuple_type resps_;
|
||||
adapters_tuple_type adapters_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
myreceiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
@@ -13,16 +12,17 @@
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapter_t;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = node<std::string>;
|
||||
using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
|
||||
struct myreceiver {
|
||||
struct receiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
receiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
@@ -52,24 +52,24 @@ public:
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push() { }
|
||||
void on_push(std::size_t n) { }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
adapter_t<response_type> adapter_;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
myreceiver recv{db};
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
client_type db(ioc.get_executor());
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db.set_receiver(recv);
|
||||
|
||||
db.async_run("127.0.0.1", "6379",
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
@@ -22,15 +21,6 @@ using aedis::adapter::adapt;
|
||||
using net::dynamic_buffer;
|
||||
using net::ip::tcp;
|
||||
|
||||
using hello_type = std::tuple<
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, int,
|
||||
std::string, int,
|
||||
std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string, std::vector<std::string>>;
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
@@ -40,25 +30,24 @@ int main()
|
||||
tcp::socket socket{ioc};
|
||||
net::connect(socket, res);
|
||||
|
||||
// Creates and sends a request to redis.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
// Creates the request and writes to the socket.
|
||||
std::string buffer;
|
||||
auto sr = make_serializer(buffer);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
net::write(socket, net::buffer(buffer));
|
||||
buffer.clear();
|
||||
|
||||
// Responses
|
||||
std::string resp;
|
||||
hello_type hello;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(hello)); // hello
|
||||
resp3::read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // quit (ignored)
|
||||
auto dbuffer = dynamic_buffer(buffer);
|
||||
resp3::read(socket, dbuffer);
|
||||
resp3::read(socket, dbuffer, adapt(resp));
|
||||
resp3::read(socket, dbuffer);
|
||||
|
||||
std::cout << std::get<0>(hello) << ": " << std::get<1>(hello) << std::endl;
|
||||
std::cout << "Ping: " << resp << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
53
examples/mystruct.hpp
Normal file
53
examples/mystruct.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
|
||||
// Arbitrary struct to de/serialize.
|
||||
struct mystruct {
|
||||
std::int32_t x;
|
||||
std::string y;
|
||||
};
|
||||
|
||||
// Serializes mystruct
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
using aedis::resp3::type;
|
||||
using aedis::resp3::add_header;
|
||||
using aedis::resp3::add_separator;
|
||||
|
||||
auto const size = sizeof obj.x + obj.y.size();
|
||||
add_header(to, type::blob_string, size);
|
||||
auto const* p = reinterpret_cast<char const*>(&obj.x);
|
||||
std::copy(p, p + sizeof obj.x, std::back_inserter(to));
|
||||
std::copy(std::cbegin(obj.y), std::cend(obj.y), std::back_inserter(to));
|
||||
add_separator(to);
|
||||
}
|
||||
|
||||
// Deserialize the struct.
|
||||
void from_string(mystruct& obj, boost::string_view sv, boost::system::error_code& ec)
|
||||
{
|
||||
char* p = reinterpret_cast<char*>(&obj.x);
|
||||
std::copy(std::cbegin(sv), std::cbegin(sv) + sizeof obj.x, p);
|
||||
std::copy(std::cbegin(sv) + sizeof obj.x, std::cend(sv), std::back_inserter(obj.y));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, mystruct const& obj)
|
||||
{
|
||||
os << "x: " << obj.x << ", y: " << obj.y;
|
||||
return os;
|
||||
}
|
||||
|
||||
bool operator<(mystruct const& a, mystruct const& b)
|
||||
{
|
||||
return std::tie(a.x, a.y) < std::tie(b.x, b.y);
|
||||
}
|
||||
47
examples/print.hpp
Normal file
47
examples/print.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
// Some functions to make the examples less repetitive.
|
||||
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::client;
|
||||
|
||||
void print_and_clear_aggregate(std::vector<aedis::resp3::node<std::string>>& v)
|
||||
{
|
||||
auto const m = aedis::resp3::element_multiplicity(v.front().data_type);
|
||||
for (auto i = 0lu; i < m * v.front().aggregate_size; ++i)
|
||||
std::cout << v[i + 1].value << " ";
|
||||
std::cout << "\n";
|
||||
v.clear();
|
||||
}
|
||||
|
||||
void print_and_clear(std::set<std::string>& cont)
|
||||
{
|
||||
for (auto const& e: cont) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
cont.clear();
|
||||
}
|
||||
|
||||
void print_and_clear(std::map<std::string, std::string>& cont)
|
||||
{
|
||||
for (auto const& e: cont)
|
||||
std::cout << e.first << ": " << e.second << "\n";
|
||||
cont.clear();
|
||||
}
|
||||
|
||||
68
examples/serialization_sync.cpp
Normal file
68
examples/serialization_sync.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <iterator>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "mystruct.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::type;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using net::ip::tcp;
|
||||
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
net::connect(socket, res);
|
||||
|
||||
// This struct will be serialized and stored on Redis.
|
||||
mystruct in{42, "Some string"};
|
||||
|
||||
// Creates and sends a request to redis.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::set, "key", in);
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
net::write(socket, net::buffer(request));
|
||||
|
||||
// Object to store the response.
|
||||
mystruct out;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
std::string buffer;
|
||||
auto dbuf = net::dynamic_buffer(buffer);
|
||||
resp3::read(socket, dbuf); // hello
|
||||
resp3::read(socket, dbuf); // set
|
||||
resp3::read(socket, dbuf, adapt(out)); // get
|
||||
resp3::read(socket, dbuf); // quit
|
||||
|
||||
// Should be equal to what has been sent above.
|
||||
std::cout << out << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -24,9 +23,7 @@ using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using response_type = std::tuple<std::string, boost::optional<std::string>>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
@@ -45,10 +42,10 @@ net::awaitable<void> example()
|
||||
|
||||
// Ignores the response to hello.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer));
|
||||
|
||||
for (std::vector<node<std::string>> resp;;) {
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp));
|
||||
for (auto const& e: resp)
|
||||
std::cout << e << std::endl;
|
||||
resp.clear();
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -35,9 +34,9 @@ using adapter_type = aedis::adapter::adapter_t<response_type>;
|
||||
* example.
|
||||
*/
|
||||
|
||||
class myreceiver {
|
||||
class receiver {
|
||||
public:
|
||||
myreceiver(client_type& db)
|
||||
receiver(client_type& db)
|
||||
: adapter_{adapt(resp_)}
|
||||
, db_{&db} {}
|
||||
|
||||
@@ -46,7 +45,7 @@ public:
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd)
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
@@ -63,7 +62,7 @@ public:
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push()
|
||||
void on_push(std::size_t)
|
||||
{
|
||||
std::cout
|
||||
<< "Event: " << resp_.at(1).value << "\n"
|
||||
@@ -84,11 +83,10 @@ int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
client_type db{ioc.get_executor()};
|
||||
myreceiver recv{db};
|
||||
auto recv = std::make_shared<receiver>(db);
|
||||
db.set_receiver(recv);
|
||||
|
||||
db.async_run(
|
||||
recv,
|
||||
{net::ip::make_address("127.0.0.1"), 6379},
|
||||
db.async_run("127.0.0.1", "6379",
|
||||
[](auto ec){ std::cout << ec.message() << std::endl;});
|
||||
|
||||
ioc.run();
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -16,16 +15,11 @@
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::redis::command;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using response_type = std::tuple<std::string, boost::optional<std::string>>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
@@ -44,17 +38,18 @@ net::awaitable<void> example()
|
||||
sr.push(command::set, "low-level-key", "some content", "EX", "2");
|
||||
sr.push(command::exec);
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
std::tuple<std::string, boost::optional<std::string>> response;
|
||||
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // ping
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(response));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
auto dbuffer = net::dynamic_buffer(buffer);
|
||||
co_await resp3::async_read(socket, dbuffer); // hellp
|
||||
co_await resp3::async_read(socket, dbuffer); // multi
|
||||
co_await resp3::async_read(socket, dbuffer); // ping
|
||||
co_await resp3::async_read(socket, dbuffer); // set
|
||||
co_await resp3::async_read(socket, dbuffer, adapt(response));
|
||||
co_await resp3::async_read(socket, dbuffer); // quit
|
||||
|
||||
std::cout
|
||||
<< "Ping: " << std::get<0>(response) << "\n"
|
||||
@@ -1,11 +1,11 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef AEDIS_USER_SESSION_HPP
|
||||
#define AEDIS_USER_SESSION_HPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -95,3 +95,4 @@ private:
|
||||
};
|
||||
|
||||
} // aedis
|
||||
#endif // AEDIS_USER_SESSION_HPP
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
@@ -19,7 +18,8 @@ void expect_eq(T const& a, T const& b, std::string const& msg = "")
|
||||
}
|
||||
}
|
||||
|
||||
void expect_error(boost::system::error_code a, boost::system::error_condition expected = {})
|
||||
template <class T>
|
||||
void expect_error(boost::system::error_code a, T expected = {})
|
||||
{
|
||||
if (a == expected) {
|
||||
if (a)
|
||||
|
||||
499
tests/high_level.cpp
Normal file
499
tests/high_level.cpp
Normal file
@@ -0,0 +1,499 @@
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/system/errc.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::adapter::adapter_t;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
using tcp = net::ip::tcp;
|
||||
using client_type = aedis::generic::client<net::ip::tcp::socket, command>;
|
||||
|
||||
auto print_read = [](auto cmd, auto n)
|
||||
{
|
||||
std::cout << cmd << ": " << n << std::endl;
|
||||
};
|
||||
|
||||
void test_resolve_error()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::netdb_errors::host_not_found);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
db.async_run("Atibaia", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
void test_connect_error()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::basic_errors::connection_refused);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
db.async_run("127.0.0.1", "1", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver1 {
|
||||
public:
|
||||
receiver1(client_type& db) : db_{&db} {}
|
||||
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
// quit will be sent more than once. It doesn't matter.
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
// Test if a hello is automatically sent.
|
||||
void test_hello()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver1 recv{db};
|
||||
db.set_read_handler([&recv](command cmd, std::size_t n){recv.on_read(cmd, n);});
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver2 {
|
||||
public:
|
||||
receiver2(client_type& db) : db_{&db} {}
|
||||
|
||||
void on_write(std::size_t)
|
||||
{
|
||||
// Notice this causes a loop, but since quit stops the
|
||||
// connection it is not a problem.
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
// Test if a hello is automatically sent but this time, uses on_write
|
||||
// to send the quit command. Notice quit will be sent twice.
|
||||
void test_hello2()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver2 recv{db};
|
||||
//db.set_read_handler(print_read);
|
||||
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver3 {
|
||||
public:
|
||||
receiver3(client_type& db) : db_{&db} {}
|
||||
|
||||
void on_write(std::size_t)
|
||||
{
|
||||
// Notice this causes a loop.
|
||||
db_->send(command::subscribe, "channel");
|
||||
}
|
||||
|
||||
void on_push(std::size_t)
|
||||
{
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
void test_push()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver3 recv{db};
|
||||
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
|
||||
db.set_push_handler([&recv](std::size_t n){recv.on_push(n);});
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver4 {
|
||||
public:
|
||||
receiver4(client_type& db) : db_{&db} {}
|
||||
|
||||
void on_read()
|
||||
{
|
||||
// Notice this causes a loop.
|
||||
db_->send(command::subscribe, "channel");
|
||||
}
|
||||
|
||||
void on_push()
|
||||
{
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
void test_push2()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
receiver4 recv{db};
|
||||
db.set_read_handler([&recv](auto, auto){recv.on_read();});
|
||||
db.set_push_handler([&recv](auto){recv.on_push();});
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
struct receiver5 {
|
||||
public:
|
||||
int counter = 0;
|
||||
|
||||
receiver5(client_type& db)
|
||||
: db_{&db}
|
||||
, adapter_{adapt(counter)}
|
||||
{}
|
||||
|
||||
void on_read(command) {}
|
||||
|
||||
void on_write()
|
||||
{
|
||||
if (counter == 0) {
|
||||
// Avoid problems with previous runs.
|
||||
db_->send(command::del, "receiver5-key");
|
||||
db_->send(command::incr, "receiver5-key");
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
if (counter == 1) {
|
||||
db_->send(command::incr, "receiver5-key");
|
||||
db_->send(command::quit);
|
||||
}
|
||||
}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
if (cmd == command::incr)
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
adapter_t<int> adapter_;
|
||||
};
|
||||
|
||||
template <class Receiver>
|
||||
struct reconnect {
|
||||
client_type db;
|
||||
Receiver recv;
|
||||
boost::asio::steady_timer timer;
|
||||
net::coroutine coro;
|
||||
|
||||
reconnect(net::any_io_executor ex)
|
||||
: db{ex}
|
||||
, recv{db}
|
||||
, timer{ex}
|
||||
{
|
||||
db.set_read_handler([this](auto cmd, auto){recv.on_read(cmd);});
|
||||
db.set_write_handler([this](auto){recv.on_write();});
|
||||
db.set_resp3_handler([this](auto a, auto b, auto c){recv.on_resp3(a, b, c);});
|
||||
}
|
||||
|
||||
void on_event(boost::system::error_code ec)
|
||||
{
|
||||
reenter (coro) for (;;) {
|
||||
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
expect_eq(recv.counter, 1, "Reconnect counter 1.");
|
||||
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
expect_eq(recv.counter, 2, "Reconnect counter 2.");
|
||||
yield db.async_run("127.0.0.1", "6379", [this](auto ec){ on_event(ec);});
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
expect_eq(recv.counter, 3, "Reconnect counter 3.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
void test_reconnect()
|
||||
{
|
||||
net::io_context ioc;
|
||||
reconnect<receiver5> rec{ioc.get_executor()};
|
||||
rec.on_event({});
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver6 {
|
||||
public:
|
||||
int counter = 0;
|
||||
|
||||
receiver6(client_type& db)
|
||||
: db_{&db}
|
||||
, adapter_{adapt(counter)}
|
||||
{}
|
||||
|
||||
void on_write() {}
|
||||
void on_read(command cmd)
|
||||
{
|
||||
if (cmd == command::hello) {
|
||||
db_->send(command::get, "receiver6-key");
|
||||
if (counter == 0)
|
||||
db_->send(command::del, "receiver6-key");
|
||||
db_->send(command::incr, "receiver6-key");
|
||||
db_->send(command::quit);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
if (cmd == command::incr)
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
private:
|
||||
client_type* db_;
|
||||
adapter_t<int> adapter_;
|
||||
};
|
||||
|
||||
void test_reconnect2()
|
||||
{
|
||||
net::io_context ioc;
|
||||
reconnect<receiver6> rec{ioc.get_executor()};
|
||||
rec.on_event({});
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver7 {
|
||||
public:
|
||||
int counter = 0;
|
||||
|
||||
receiver7(client_type& db)
|
||||
: db_{&db}
|
||||
, adapter_{adapt(counter)}
|
||||
{}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
if (cmd == command::incr)
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_write(std::size_t)
|
||||
{
|
||||
if (!std::exchange(sent_, true)) {
|
||||
db_->send(command::del, "key");
|
||||
db_->send(command::multi);
|
||||
db_->send(command::ping, "aaa");
|
||||
db_->send(command::incr, "key");
|
||||
db_->send(command::ping, "bbb");
|
||||
db_->send(command::discard);
|
||||
db_->send(command::ping, "ccc");
|
||||
db_->send(command::incr, "key");
|
||||
db_->send(command::quit);
|
||||
}
|
||||
}
|
||||
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
bool sent_ = false;
|
||||
client_type* db_;
|
||||
adapter_t<int> adapter_;
|
||||
};
|
||||
|
||||
void test_discard()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type db(ioc.get_executor());
|
||||
|
||||
receiver7 recv{db};
|
||||
db.set_read_handler([&recv](auto cmd, std::size_t n){recv.on_read(cmd, n);});
|
||||
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
|
||||
db.set_resp3_handler([&recv](auto a, auto b, auto c){recv.on_resp3(a, b, c);});
|
||||
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
|
||||
expect_eq(recv.counter, 1, "test_discard.");
|
||||
}
|
||||
|
||||
struct receiver8 {
|
||||
public:
|
||||
receiver8(client_type& db) : db_{&db} {}
|
||||
|
||||
void on_write(std::size_t)
|
||||
{
|
||||
std::cout << "on_write" << std::endl;
|
||||
if (!std::exchange(sent_, true)) {
|
||||
db_->send(command::del, "key");
|
||||
db_->send(command::client, "PAUSE", 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool sent_ = false;
|
||||
client_type* db_;
|
||||
};
|
||||
|
||||
void test_idle()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, aedis::generic::error::idle_timeout);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type::config cfg;
|
||||
cfg.resolve_timeout = std::chrono::seconds{1};
|
||||
cfg.connect_timeout = std::chrono::seconds{1};
|
||||
cfg.read_timeout = std::chrono::seconds{1};
|
||||
cfg.write_timeout = std::chrono::seconds{1};
|
||||
cfg.idle_timeout = std::chrono::seconds{2};
|
||||
client_type db(ioc.get_executor(), cfg);
|
||||
|
||||
receiver8 recv{db};
|
||||
db.set_write_handler([&recv](std::size_t n){recv.on_write(n);});
|
||||
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
struct receiver9 {
|
||||
public:
|
||||
bool ping = false;
|
||||
|
||||
receiver9(client_type& db) : db_{&db} , adapter_{adapt(counter_)} {}
|
||||
|
||||
void on_resp3(command cmd, node<boost::string_view> const& nd, boost::system::error_code& ec)
|
||||
{
|
||||
if (cmd == command::incr)
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_push(std::size_t) {}
|
||||
|
||||
void on_write(std::size_t)
|
||||
{
|
||||
if (!std::exchange(sent_, true))
|
||||
db_->send(command::del, "key");
|
||||
|
||||
db_->send(command::incr, "key");
|
||||
db_->send(command::subscribe, "channel");
|
||||
}
|
||||
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
db_->send(command::incr, "key");
|
||||
db_->send(command::subscribe, "channel");
|
||||
if (counter_ == 100000) {
|
||||
std::cout << "Success: counter increase." << std::endl;
|
||||
db_->send(command::quit);
|
||||
}
|
||||
|
||||
if (cmd == command::ping)
|
||||
ping = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool sent_ = false;
|
||||
client_type* db_;
|
||||
int counter_ = 0;
|
||||
adapter_t<int> adapter_;
|
||||
};
|
||||
|
||||
void test_no_ping()
|
||||
{
|
||||
auto f = [](auto ec)
|
||||
{
|
||||
expect_error(ec, net::error::misc_errors::eof);
|
||||
};
|
||||
|
||||
net::io_context ioc;
|
||||
client_type::config cfg;
|
||||
cfg.idle_timeout = std::chrono::seconds{2};
|
||||
client_type db(ioc.get_executor(), cfg);
|
||||
|
||||
auto recv = std::make_shared<receiver9>(db);
|
||||
db.set_receiver(recv);
|
||||
db.async_run("127.0.0.1", "6379", f);
|
||||
ioc.run();
|
||||
|
||||
expect_eq(recv->ping, false, "No ping received.");
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_resolve_error();
|
||||
test_connect_error();
|
||||
test_hello();
|
||||
test_hello2();
|
||||
test_push();
|
||||
test_push2();
|
||||
test_reconnect();
|
||||
test_reconnect2();
|
||||
test_discard();
|
||||
test_no_ping();
|
||||
|
||||
// Must come last as it send a client pause.
|
||||
test_idle();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
@@ -37,7 +36,7 @@ struct expect {
|
||||
std::string in;
|
||||
Result expected;
|
||||
std::string name;
|
||||
boost::system::error_condition ec;
|
||||
boost::system::error_code ec;
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
@@ -109,12 +108,12 @@ void test_number(net::io_context& ioc)
|
||||
auto const in03 = expect<int>{":11\r\n", int{11}, "number.int"};
|
||||
auto const in04 = expect<boost::optional<int>>{":11\r\n", ok, "number.optional.int"};
|
||||
auto const in05 = expect<std::tuple<int>>{"*1\r\n:11\r\n", std::tuple<int>{11}, "number.tuple.int"};
|
||||
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_aggregate)};
|
||||
auto const in06 = expect<boost::optional<int>>{"%11\r\n", boost::optional<int>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in07 = expect<std::set<std::string>>{":11\r\n", std::set<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
|
||||
auto const in08 = expect<std::unordered_set<std::string>>{":11\r\n", std::unordered_set<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
|
||||
auto const in09 = expect<std::map<std::string, std::string>>{":11\r\n", std::map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
|
||||
auto const in10 = expect<std::unordered_map<std::string, std::string>>{":11\r\n", std::unordered_map<std::string, std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
|
||||
auto const in11 = expect<std::list<std::string>>{":11\r\n", std::list<std::string>{}, "number.optional.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_aggregate_type)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -156,11 +155,11 @@ void test_bool(net::io_context& ioc)
|
||||
auto const in13 = expect<boost::optional<bool>>{"#t\r\n", ok, "optional.int"};
|
||||
|
||||
// Error
|
||||
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::unexpected_bool_value)};
|
||||
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_set_aggregate)};
|
||||
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::adapter::make_error_condition(aedis::adapter::error::expects_map_like_aggregate)};
|
||||
auto const in01 = expect<boost::optional<bool>>{"#11\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_code(aedis::resp3::error::unexpected_bool_value)};
|
||||
auto const in03 = expect<std::set<int>>{"#t\r\n", std::set<int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
|
||||
auto const in04 = expect<std::unordered_set<int>>{"#t\r\n", std::unordered_set<int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_set_type)};
|
||||
auto const in05 = expect<std::map<int, int>>{"#t\r\n", std::map<int, int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
|
||||
auto const in06 = expect<std::unordered_map<int, int>>{"#t\r\n", std::unordered_map<int, int>{}, "bool.error", aedis::adapter::make_error_code(aedis::adapter::error::expects_map_type)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -320,7 +319,7 @@ void test_map(net::io_context& ioc)
|
||||
auto const in07 = expect<op_map_type>{wire, expected_1d, "map.optional.map"};
|
||||
auto const in08 = expect<op_vec_type>{wire, expected_1e, "map.optional.vector"};
|
||||
auto const in09 = expect<std::tuple<op_map_type>>{"*1\r\n" + wire, std::tuple<op_map_type>{expected_1d}, "map.transaction.optional.map"};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::adapter::make_error_condition(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in10 = expect<int>{"%11\r\n", int{}, "map.invalid.int", aedis::adapter::make_error_code(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
@@ -534,19 +533,23 @@ void test_blob_string(net::io_context& ioc)
|
||||
|
||||
void test_double(net::io_context& ioc)
|
||||
{
|
||||
// TODO: Add test for double.
|
||||
auto const in01 = expect<node_type>{",1.23\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}, "double.node"};
|
||||
auto const in02 = expect<node_type>{",inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}, "double.node (inf)"};
|
||||
auto const in03 = expect<node_type>{",-inf\r\n", node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}, "double.node (-inf)"};
|
||||
auto const in04 = expect<double>{",1.23\r\n", double{1.23}, "double.double"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
}
|
||||
|
||||
void test_blob_error(net::io_context& ioc)
|
||||
@@ -580,7 +583,7 @@ void test_verbatim_string(net::io_context& ioc)
|
||||
void test_big_number(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"(3492890328409238509324850943850943825024385\r\n", node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}, "big_number.node"};
|
||||
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in02 = expect<int>{"(\r\n", int{}, "big_number.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
@@ -617,11 +620,11 @@ void test_simple_string(net::io_context& ioc)
|
||||
|
||||
void test_resp3(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::resp3::make_error_condition(aedis::resp3::error::invalid_type)};
|
||||
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::resp3::make_error_condition(aedis::resp3::error::not_a_number)};
|
||||
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::resp3::make_error_condition(aedis::resp3::error::empty_field)};
|
||||
auto const in01 = expect<int>{"s11\r\n", int{}, "number.error", aedis::resp3::make_error_code(aedis::resp3::error::invalid_type)};
|
||||
auto const in02 = expect<int>{":adf\r\n", int{11}, "number.int", aedis::resp3::make_error_code(aedis::resp3::error::not_a_number)};
|
||||
auto const in03 = expect<int>{":\r\n", int{}, "number.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
|
||||
auto const in04 = expect<boost::optional<bool>>{"#\r\n", boost::optional<bool>{}, "bool.error", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
|
||||
auto const in05 = expect<std::string>{",\r\n", std::string{}, "double.error (empty field)", aedis::resp3::make_error_code(aedis::resp3::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
|
||||
403
tests/online.cpp
403
tests/online.cpp
@@ -1,403 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
#include <boost/system/errc.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
using tcp = net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<tcp::socket>;
|
||||
|
||||
std::vector<node_type> gresp;
|
||||
|
||||
net::awaitable<void>
|
||||
test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
std::vector<int> list_ {1 ,2, 3, 4, 5, 6};
|
||||
std::string set_ {"aaa"};
|
||||
|
||||
//----------------------------------
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "a", list_);
|
||||
sr.push(command::llen, "a");
|
||||
sr.push(command::lrange, "a", 0, -1);
|
||||
sr.push(command::ltrim, "a", 2, -2);
|
||||
sr.push(command::lpop, "a");
|
||||
//sr.lpop("a", 2); // Not working?
|
||||
sr.push(command::set, "b", set_);
|
||||
sr.push(command::get, "b");
|
||||
sr.push(command::append, "b", "b");
|
||||
sr.push(command::del, "b");
|
||||
sr.push(command::subscribe, "channel");
|
||||
sr.push(command::incr, "3");
|
||||
|
||||
// transaction
|
||||
for (auto i = 0; i < 3; ++i) {
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::lrange, "a", 0, -1);
|
||||
sr.push(command::ping);
|
||||
// TODO: It looks like we can't publish to a channel we
|
||||
// are already subscribed to from inside a transaction.
|
||||
//req.publish("some-channel", "message1");
|
||||
sr.push(command::exec);
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> m1 =
|
||||
{ {"field1", "value1"}
|
||||
, {"field2", "value2"}};
|
||||
|
||||
sr.push_range(command::hset, "d", m1);
|
||||
sr.push(command::hget, "d", "field2");
|
||||
sr.push(command::hgetall, "d");
|
||||
sr.push(command::hdel, "d", "field1", "field2"); // TODO: Test as range too.
|
||||
sr.push(command::hincrby, "e", "some-field", 10);
|
||||
|
||||
sr.push(command::zadd, "f", 1, "Marcelo");
|
||||
sr.push(command::zrange, "f", 0, 1);
|
||||
sr.push(command::zrangebyscore, "f", 1, 1);
|
||||
sr.push(command::zremrangebyscore, "f", "-inf", "+inf");
|
||||
|
||||
auto const v = std::vector<int>{1, 2, 3};
|
||||
sr.push_range(command::sadd, "g", v);
|
||||
sr.push(command::smembers, "g");
|
||||
sr.push(command::quit);
|
||||
//----------------------------------
|
||||
|
||||
net::ip::tcp::socket socket{ex};
|
||||
co_await net::async_connect(socket, res, net::use_awaitable);
|
||||
co_await net::async_write(socket, net::buffer(request), net::use_awaitable);
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
|
||||
// hello
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
|
||||
|
||||
// flushall
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(), net::use_awaitable);
|
||||
|
||||
{ // rpush:
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
auto const n = std::to_string(std::size(list_));
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, n} };
|
||||
|
||||
expect_eq(resp, expected, "rpush (value)");
|
||||
}
|
||||
|
||||
{ // llen
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, {"6"}} };
|
||||
expect_eq(resp, expected, "llen");
|
||||
}
|
||||
|
||||
{ // lrange
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::array, 6UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"4"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"5"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"6"}}
|
||||
};
|
||||
|
||||
expect_eq(resp, expected, "lrange ");
|
||||
}
|
||||
|
||||
{ // ltrim
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
expect_eq(resp, expected, "ltrim");
|
||||
}
|
||||
|
||||
{ // lpop
|
||||
std::vector<node_type> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node_type> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
|
||||
|
||||
expect_eq(resp, expected, "lpop");
|
||||
}
|
||||
|
||||
//{ // lpop
|
||||
// std::vector<node_type> resp;
|
||||
// co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"4"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"5"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "lpop");
|
||||
//}
|
||||
|
||||
//{ // lrange
|
||||
// static int c = 0;
|
||||
|
||||
// if (c == 0) {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 6UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"4"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"5"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"6"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "lrange ");
|
||||
// } else {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// expect_eq(resp, expected, "lrange (inside transaction)");
|
||||
// }
|
||||
//
|
||||
// ++c;
|
||||
//}
|
||||
|
||||
//for (;;) {
|
||||
// switch (cmd) {
|
||||
// case command::multi:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// expect_eq(resp, expected, "multi");
|
||||
// } break;
|
||||
// case command::ping:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// expect_eq(resp, expected, "ping");
|
||||
// } break;
|
||||
// case command::set:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// expect_eq(resp, expected, "set");
|
||||
// } break;
|
||||
// case command::quit:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// expect_eq(resp, expected, "quit");
|
||||
// } break;
|
||||
// case command::flushall:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// expect_eq(resp, expected, "flushall");
|
||||
// } break;
|
||||
// case command::append:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"4"}} };
|
||||
|
||||
// expect_eq(resp, expected, "append");
|
||||
// } break;
|
||||
// case command::hset:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// expect_eq(resp, expected, "hset");
|
||||
// } break;
|
||||
// case command::del:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// expect_eq(resp, expected, "del");
|
||||
// } break;
|
||||
// case command::incr:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// expect_eq(resp, expected, "incr");
|
||||
// } break;
|
||||
// case command::publish:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// expect_eq(resp, expected, "publish");
|
||||
// } break;
|
||||
// case command::hincrby:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"10"}} };
|
||||
|
||||
// expect_eq(resp, expected, "hincrby");
|
||||
// } break;
|
||||
// case command::zadd:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// expect_eq(resp, expected, "zadd");
|
||||
// } break;
|
||||
// case command::sadd:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"3"}} };
|
||||
|
||||
// expect_eq(resp, expected, "sadd");
|
||||
// } break;
|
||||
// case command::hdel:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// expect_eq(resp, expected, "hdel");
|
||||
// } break;
|
||||
// case command::zremrangebyscore:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// expect_eq(resp, expected, "zremrangebyscore");
|
||||
// } break;
|
||||
// case command::get:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, test.set_} };
|
||||
|
||||
// expect_eq(resp, expected, "get");
|
||||
// } break;
|
||||
// case command::hget:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, std::string{"value2"}} };
|
||||
|
||||
// expect_eq(resp, expected, "hget");
|
||||
// } break;
|
||||
// case command::hvals:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value1"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value2"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrange:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrangebyscore:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "zrangebyscore");
|
||||
// } break;
|
||||
// case command::exec:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::array, 3UL, 0UL, {}}
|
||||
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
|
||||
// , {resp3::type::array, 2UL, 1UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 2UL, {"4"}}
|
||||
// , {resp3::type::blob_string, 1UL, 2UL, {"5"}}
|
||||
// , {resp3::type::simple_string, 1UL, 1UL, {"PONG"}}
|
||||
// };
|
||||
|
||||
// expect_eq(resp, expected, "transaction");
|
||||
|
||||
// } break;
|
||||
// case command::hgetall:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::map, 2UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"field1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"field2"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
|
||||
// };
|
||||
// expect_eq(resp, expected, "hgetall (value)");
|
||||
// } break;
|
||||
// case command::smembers:
|
||||
// {
|
||||
// std::vector<node_type> expected
|
||||
// { {resp3::type::set, 3UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"1"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"2"}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
// };
|
||||
// expect_eq(resp, expected, "smembers (value)");
|
||||
// } break;
|
||||
// default: { std::cout << "Error: " << resp.front().data_type << " " << cmd << std::endl; }
|
||||
// }
|
||||
|
||||
// resp.clear();
|
||||
//}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc {1};
|
||||
tcp::resolver resv(ioc);
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
|
||||
co_spawn(ioc, test_general(res), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres.gmail.com)
|
||||
/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
Reference in New Issue
Block a user