mirror of
https://github.com/boostorg/redis.git
synced 2026-01-27 19:22:07 +00:00
Compare commits
106 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 | ||
|
|
19f03e2f41 | ||
|
|
0fff6496fb | ||
|
|
57ba376544 | ||
|
|
46b86c20e5 | ||
|
|
577c6d35fb | ||
|
|
42ef8a3b06 | ||
|
|
f9af8e585b | ||
|
|
350ae19936 | ||
|
|
57f2644903 | ||
|
|
0ac0c3cf23 | ||
|
|
5c23299a8a | ||
|
|
379da7a340 | ||
|
|
bcf13c03c0 | ||
|
|
be79f58808 | ||
|
|
2c96b07623 | ||
|
|
577d32f0e2 | ||
|
|
5f93257502 | ||
|
|
409ee61522 | ||
|
|
5aa034d6a5 | ||
|
|
0ff0e537ce | ||
|
|
320ee6b3cc | ||
|
|
5061e5a7a6 | ||
|
|
0bda78dd9c | ||
|
|
8704e7756f | ||
|
|
8207ef7e8a | ||
|
|
4712872daf | ||
|
|
06752ac664 | ||
|
|
b3be596f77 | ||
|
|
f92290be16 | ||
|
|
331010c240 | ||
|
|
11b697c572 | ||
|
|
1218b2ef01 | ||
|
|
8cc142d55b | ||
|
|
0723922ac1 | ||
|
|
54ba34c70c | ||
|
|
efe44fbd45 | ||
|
|
268b59afbc | ||
|
|
6b6272694c | ||
|
|
f36f3b7b5e | ||
|
|
b4fef73b87 | ||
|
|
c27b3b6b85 | ||
|
|
feb383d7e2 | ||
|
|
ee71ff885d | ||
|
|
80a80f44ff | ||
|
|
053ce3aea9 | ||
|
|
db27e3cc60 | ||
|
|
449ba0dd73 | ||
|
|
8728f58981 | ||
|
|
2573f39aa1 | ||
|
|
f460bf43a5 | ||
|
|
85d0a30dad | ||
|
|
b7f22d8c61 | ||
|
|
b04cc12d64 | ||
|
|
317690ee91 | ||
|
|
fe3cc2efb0 |
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.
|
||||
85
Makefile.am
85
Makefile.am
@@ -6,30 +6,31 @@ DISTCHECK_CONFIGURE_FLAGS = CPPFLAGS="$(BOOST_CPPFLAGS) $(CPPFLAGS)" LDFLAGS="$(
|
||||
|
||||
AM_CPPFLAGS =
|
||||
AM_CPPFLAGS += $(BOOST_CPPFLAGS)
|
||||
#AM_CPPFLAGS += -I$(top_srcdir)/include
|
||||
|
||||
AM_LDFLAGS =
|
||||
AM_LDFLAGS += -pthread
|
||||
|
||||
check_PROGRAMS =
|
||||
check_PROGRAMS += intro
|
||||
check_PROGRAMS += sets
|
||||
check_PROGRAMS += hashes
|
||||
check_PROGRAMS += serialization
|
||||
check_PROGRAMS += multipurpose_response
|
||||
check_PROGRAMS += lists
|
||||
check_PROGRAMS += key_expiration
|
||||
check_PROGRAMS += response_adapter
|
||||
check_PROGRAMS += sync
|
||||
check_PROGRAMS += multipurpose_client
|
||||
check_PROGRAMS += test_offline
|
||||
check_PROGRAMS += test_online
|
||||
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 += transaction
|
||||
check_PROGRAMS += custom_adapter
|
||||
endif
|
||||
|
||||
EXTRA_PROGRAMS =
|
||||
EXTRA_PROGRAMS += subscriber_high_level
|
||||
EXTRA_PROGRAMS += commands
|
||||
if HAVE_CXX20
|
||||
EXTRA_PROGRAMS += subscriber
|
||||
EXTRA_PROGRAMS += echo_server
|
||||
EXTRA_PROGRAMS += chat_room
|
||||
EXTRA_PROGRAMS += commands
|
||||
endif
|
||||
|
||||
CLEANFILES =
|
||||
CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
@@ -37,55 +38,55 @@ CLEANFILES += $(EXTRA_PROGRAMS)
|
||||
.PHONY: all
|
||||
all: $(check_PROGRAMS) $(EXTRA_PROGRAMS)
|
||||
|
||||
intro_SOURCES = $(top_srcdir)/examples/intro.cpp
|
||||
sets_SOURCES = $(top_srcdir)/examples/sets.cpp
|
||||
hashes_SOURCES = $(top_srcdir)/examples/hashes.cpp
|
||||
serialization_SOURCES = $(top_srcdir)/examples/serialization.cpp
|
||||
multipurpose_response_SOURCES = $(top_srcdir)/examples/multipurpose_response.cpp
|
||||
lists_SOURCES = $(top_srcdir)/examples/lists.cpp
|
||||
key_expiration_SOURCES = $(top_srcdir)/examples/key_expiration.cpp
|
||||
response_adapter_SOURCES = $(top_srcdir)/examples/response_adapter.cpp
|
||||
sync_SOURCES = $(top_srcdir)/examples/sync.cpp
|
||||
multipurpose_client_SOURCES = $(top_srcdir)/examples/multipurpose_client.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
|
||||
test_offline_SOURCES = $(top_srcdir)/tests/offline.cpp
|
||||
test_online_SOURCES = $(top_srcdir)/tests/online.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
|
||||
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
|
||||
transaction_SOURCES = $(top_srcdir)/examples/transaction.cpp
|
||||
endif
|
||||
|
||||
nobase_include_HEADERS =\
|
||||
$(top_srcdir)/aedis/config.hpp\
|
||||
$(top_srcdir)/aedis/src.hpp\
|
||||
$(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\
|
||||
$(top_srcdir)/aedis/resp3/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/adapt.hpp\
|
||||
$(top_srcdir)/aedis/resp3/detail/composer.hpp\
|
||||
$(top_srcdir)/aedis/adapter/detail/adapters.hpp\
|
||||
$(top_srcdir)/aedis/adapter/error.hpp\
|
||||
$(top_srcdir)/aedis/adapter/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/adapter/adapt.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\
|
||||
$(top_srcdir)/aedis/resp3/detail/parser.hpp\
|
||||
$(top_srcdir)/aedis/resp3/serializer.hpp\
|
||||
$(top_srcdir)/aedis/resp3/response_traits.hpp\
|
||||
$(top_srcdir)/aedis/resp3/node.hpp\
|
||||
$(top_srcdir)/aedis/resp3/error.hpp\
|
||||
$(top_srcdir)/aedis/resp3/impl/error.ipp\
|
||||
$(top_srcdir)/aedis/resp3/type.hpp\
|
||||
$(top_srcdir)/aedis/resp3/read.hpp\
|
||||
$(top_srcdir)/aedis/redis/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/sentinel/impl/command.ipp\
|
||||
$(top_srcdir)/aedis/resp3/detail/impl/parser.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp\
|
||||
$(top_srcdir)/aedis/resp3/impl/node.ipp\
|
||||
$(top_srcdir)/aedis/redis/experimental/client.hpp\
|
||||
$(top_srcdir)/aedis/redis/experimental/impl/client.ipp
|
||||
$(top_srcdir)/aedis/resp3/impl/type.ipp
|
||||
|
||||
nobase_noinst_HEADERS =\
|
||||
$(top_srcdir)/examples/lib/net_utils.hpp\
|
||||
$(top_srcdir)/examples/lib/user_session.hpp\
|
||||
$(top_srcdir)/tests/check.hpp\
|
||||
$(top_srcdir)/tests/test_stream.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)
|
||||
|
||||
|
||||
82
aedis/adapter/adapt.hpp
Normal file
82
aedis/adapter/adapt.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/* 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_ADAPTER_ADAPT_HPP
|
||||
#define AEDIS_ADAPTER_ADAPT_HPP
|
||||
|
||||
#include <aedis/adapter/detail/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace 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. It is
|
||||
useful to avoid wasting time with responses which are not needed.
|
||||
|
||||
Example:
|
||||
|
||||
@code
|
||||
// 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 detail::response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* STL containers, \c std::tuple and built-in types are supported and
|
||||
* can be used in conjunction with \c boost::optional<T>.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
* sr.push(command::multi);
|
||||
* sr.push(command::ping, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push_range(command::rpush, ...);
|
||||
* sr.push(command::lrange, ...);
|
||||
* sr.push(command::incr, ...);
|
||||
* sr.push(command::exec);
|
||||
*
|
||||
* co_await async_write(socket, buffer(request));
|
||||
*
|
||||
* // Reads the response to a transaction
|
||||
* std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
* co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return detail::response_traits<T>::adapt(t); }
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPT_HPP
|
||||
428
aedis/adapter/detail/adapters.hpp
Normal file
428
aedis/adapter/detail/adapters.hpp
Normal file
@@ -0,0 +1,428 @@
|
||||
/* 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_ADAPTER_ADAPTERS_HPP
|
||||
#define AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <forward_list>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#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>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/generic/serializer.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
|
||||
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>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
i = resp3::detail::parse_uint(sv.data(), sv.size(), ec);
|
||||
}
|
||||
|
||||
void from_string(
|
||||
bool& t,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
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(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
boost::string_view sv,
|
||||
boost::system::error_code&)
|
||||
{
|
||||
s.append(sv.data(), sv.size());
|
||||
}
|
||||
|
||||
//================================================
|
||||
|
||||
void set_on_resp3_error(resp3::type t, boost::system::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case resp3::type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case resp3::type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case resp3::type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class general_aggregate {
|
||||
private:
|
||||
Result* result_;
|
||||
|
||||
public:
|
||||
general_aggregate(Result* c = nullptr): result_(c) {}
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->push_back({n.data_type, n.aggregate_size, n.depth, std::string{std::cbegin(n.value), std::cend(n.value)}});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class general_simple {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
general_simple(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void operator()(resp3::node<boost::string_view> const& n, boost::system::error_code&)
|
||||
{
|
||||
result_->data_type = n.data_type;
|
||||
result_->aggregate_size = n.aggregate_size;
|
||||
result_->depth = n.depth;
|
||||
result_->value.assign(n.value.data(), n.value.size());
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class simple_impl {
|
||||
public:
|
||||
void on_value_available(Result&) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& n,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(n.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(n.data_type)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
from_string(result, n.value, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class set_impl {
|
||||
private:
|
||||
typename Result::iterator hint_;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ hint_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (nd.data_type != resp3::type::set)
|
||||
ec = error::expects_set_type;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_set_type;
|
||||
return;
|
||||
}
|
||||
|
||||
typename Result::key_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
hint_ = result.insert(hint_, std::move(obj));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class map_impl {
|
||||
private:
|
||||
typename Result::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& result)
|
||||
{ current_ = std::end(result); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (element_multiplicity(nd.data_type) != 2)
|
||||
ec = error::expects_map_type;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_map_type;
|
||||
return;
|
||||
}
|
||||
|
||||
if (on_key_) {
|
||||
typename Result::key_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
current_ = result.insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Result::mapped_type obj;
|
||||
from_string(obj, nd.value, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class vector_impl {
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
auto const m = element_multiplicity(nd.data_type);
|
||||
result.reserve(result.size() + m * nd.aggregate_size);
|
||||
} else {
|
||||
result.push_back({});
|
||||
from_string(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
class array_impl {
|
||||
private:
|
||||
int i_ = -1;
|
||||
|
||||
public:
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (is_aggregate(nd.data_type)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_aggregate_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.size() != nd.aggregate_size * element_multiplicity(nd.data_type)) {
|
||||
ec = error::incompatible_size;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i_ == -1) {
|
||||
ec = adapter::error::expects_aggregate_type;
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
from_string(result.at(i_), nd.value, ec);
|
||||
}
|
||||
|
||||
++i_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
struct list_impl {
|
||||
|
||||
void on_value_available(Result& ) { }
|
||||
|
||||
void
|
||||
operator()(
|
||||
Result& result,
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(nd.data_type, ec);
|
||||
if (ec)
|
||||
return;
|
||||
|
||||
if (!is_aggregate(nd.data_type)) {
|
||||
BOOST_ASSERT(nd.aggregate_size == 1);
|
||||
if (nd.depth < 1) {
|
||||
ec = adapter::error::expects_aggregate_type;
|
||||
return;
|
||||
}
|
||||
|
||||
result.push_back({});
|
||||
from_string(result.back(), nd.value, ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
struct impl_map { using type = simple_impl<T>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::set<Key, Compare, Allocator>> { using type = set_impl<std::set<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct impl_map<std::multiset<Key, Compare, Allocator>> { using type = set_impl<std::multiset<Key, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_set<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_set<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>> { using type = set_impl<std::unordered_multiset<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::map<Key, T, Compare, Allocator>> { using type = map_impl<std::map<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct impl_map<std::multimap<Key, T, Compare, Allocator>> { using type = map_impl<std::multimap<Key, T, Compare, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_map<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_map<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct impl_map<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>> { using type = map_impl<std::unordered_multimap<Key, Hash, KeyEqual, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::vector<T, Allocator>> { using type = vector_impl<std::vector<T, Allocator>>; };
|
||||
|
||||
template <class T, std::size_t N>
|
||||
struct impl_map<std::array<T, N>> { using type = array_impl<std::array<T, N>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::list<T, Allocator>> { using type = list_impl<std::list<T, Allocator>>; };
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct impl_map<std::deque<T, Allocator>> { using type = list_impl<std::deque<T, Allocator>>; };
|
||||
|
||||
//---------------------------------------------------
|
||||
|
||||
template <class Result>
|
||||
class wrapper {
|
||||
private:
|
||||
Result* result_;
|
||||
typename impl_map<Result>::type impl_;
|
||||
|
||||
public:
|
||||
wrapper(Result* t = nullptr) : result_(t)
|
||||
{ impl_.on_value_available(*result_); }
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
BOOST_ASSERT(result_);
|
||||
impl_(*result_, nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class wrapper<boost::optional<T>> {
|
||||
private:
|
||||
boost::optional<T>* result_;
|
||||
typename impl_map<T>::type impl_;
|
||||
|
||||
public:
|
||||
wrapper(boost::optional<T>* o = nullptr) : result_(o), impl_{} {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
if (nd.data_type == resp3::type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value()) {
|
||||
*result_ = T{};
|
||||
impl_.on_value_available(result_->value());
|
||||
}
|
||||
|
||||
impl_(result_->value(), nd, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_ADAPTERS_HPP
|
||||
161
aedis/adapter/detail/response_traits.hpp
Normal file
161
aedis/adapter/detail/response_traits.hpp
Normal file
@@ -0,0 +1,161 @@
|
||||
/* 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_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
#define AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
#include <boost/variant2.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/adapter/detail/adapters.hpp>
|
||||
#include <aedis/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
/* Traits class for response objects.
|
||||
*
|
||||
* Provides traits for all supported response types i.e. all STL
|
||||
* containers and C++ buil-in types.
|
||||
*/
|
||||
template <class ResponseType>
|
||||
struct response_traits
|
||||
{
|
||||
using adapter_type = adapter::detail::wrapper<ResponseType>;
|
||||
static auto adapt(ResponseType& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using adapter_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<resp3::node<T>>
|
||||
{
|
||||
using response_type = resp3::node<T>;
|
||||
using adapter_type = adapter::detail::general_simple<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class String, class Allocator>
|
||||
struct response_traits<std::vector<resp3::node<String>, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<resp3::node<String>, Allocator>;
|
||||
using adapter_type = adapter::detail::general_aggregate<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = resp3::detail::ignore_response;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N] = internal_adapt(std::get<N>(from));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
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_type adapters_;
|
||||
|
||||
public:
|
||||
static_aggregate_adapter(Tuple* r = nullptr)
|
||||
{
|
||||
detail::assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r);
|
||||
}
|
||||
|
||||
void count(resp3::node<boost::string_view> const& nd)
|
||||
{
|
||||
if (nd.depth == 1) {
|
||||
if (is_aggregate(nd.data_type))
|
||||
aggregate_size_ = element_multiplicity(nd.data_type) * nd.aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
resp3::node<boost::string_view> const& nd,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
using boost::variant2::visit;
|
||||
|
||||
if (nd.depth == 0) {
|
||||
auto const real_aggr_size = nd.aggregate_size * element_multiplicity(nd.data_type);
|
||||
if (real_aggr_size != std::tuple_size<Tuple>::value)
|
||||
ec = error::incompatible_size;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
visit([&](auto& arg){arg(nd, ec);}, adapters_[i_]);
|
||||
count(nd);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = detail::static_aggregate_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_ADAPTER_RESPONSE_TRAITS_HPP
|
||||
66
aedis/adapter/error.hpp
Normal file
66
aedis/adapter/error.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* 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_ADAPTER_ERROR_HPP
|
||||
#define AEDIS_ADAPTER_ERROR_HPP
|
||||
|
||||
#include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Adapter errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Expects aggregate type.
|
||||
expects_aggregate_type,
|
||||
|
||||
/// Expects a map but got other aggregate.
|
||||
expects_map_type,
|
||||
|
||||
/// Expects a set aggregate but got something else.
|
||||
expects_set_type,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_aggregate_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// Aggregate container has incompatible size.
|
||||
incompatible_size,
|
||||
|
||||
/// Not a double
|
||||
not_a_double,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
/** \brief Creates a error_code object from an error.
|
||||
* \ingroup any
|
||||
*/
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_ADAPTER_ERROR_HPP
|
||||
53
aedis/adapter/impl/error.ipp
Normal file
53
aedis/adapter/impl/error.ipp
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 <system_error>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{
|
||||
return "aedis.adapter";
|
||||
}
|
||||
|
||||
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_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: 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()};
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // aedis
|
||||
888
aedis/aedis.hpp
888
aedis/aedis.hpp
@@ -1,97 +1,583 @@
|
||||
/* 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/config.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/resp3/type.hpp>
|
||||
#include <aedis/resp3/read.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
#include <aedis/generic/error.hpp>
|
||||
#include <aedis/generic/client.hpp>
|
||||
#include <aedis/generic/serializer.hpp>
|
||||
|
||||
/** \mainpage Documentation
|
||||
\tableofcontents
|
||||
|
||||
\section Overview
|
||||
|
||||
Aedis is low-level redis client library built on top of Boost.Asio
|
||||
that implements communication with a Redis server over its native
|
||||
protocol RESP3. It has first-class support for STL containers and
|
||||
C++ built in types among other things. You will be able to
|
||||
implement your own redis client or use a general purpose provided
|
||||
by the library. For more information about Redis see
|
||||
https://redis.io/
|
||||
|
||||
Aedis is a [Redis](https://redis.io/) client library built on top
|
||||
of [Asio](https://www.boost.org/doc/libs/release/doc/html/boost_asio.html)
|
||||
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/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
|
||||
|
||||
@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 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 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 coroutine-based asynchronous implementation of the steps above look like
|
||||
|
||||
@code
|
||||
net::awaitable<std::string> set(net::ip::tcp::endpoint ep)
|
||||
{
|
||||
// To make code less verbose
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
tcp_socket socket{co_await net::this_coro::executor};
|
||||
co_await socket.async_connect(ep);
|
||||
|
||||
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(buffer));
|
||||
buffer.clear();
|
||||
|
||||
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;
|
||||
}
|
||||
@endcode
|
||||
|
||||
The simplicity of the code above makes it self explanatory
|
||||
|
||||
@li Connect to the Redis server.
|
||||
@li Declare a \c std::string to hold the request and add some commands in it with a serializer.
|
||||
@li Write the payload to the socket and read the responses in the same order they were sent.
|
||||
@li Return the response to the user.
|
||||
|
||||
The @c hello command above is always required and must be sent
|
||||
first as it informs we want to communicate over RESP3.
|
||||
|
||||
\subsection requests Requests
|
||||
|
||||
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
|
||||
|
||||
@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.
|
||||
std::string value = "some value";
|
||||
|
||||
std::list<std::string> list {"channel1", "channel2", "channel3"};
|
||||
|
||||
std::map<std::string, mystruct> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}};
|
||||
|
||||
// Command with no arguments
|
||||
sr.push(command::quit);
|
||||
|
||||
// Command with variable lenght arguments.
|
||||
sr.push(command::set, "key", value, "EX", "2");
|
||||
|
||||
// Sends a container, no key.
|
||||
sr.push_range(command::subscribe, list);
|
||||
|
||||
// Same as above but an iterator range.
|
||||
sr.push_range2(command::subscribe, std::cbegin(list), std::cend(list));
|
||||
|
||||
// Sends a container, with key.
|
||||
sr.push_range(command::hset, "key", map);
|
||||
|
||||
// Same as above but as iterator range.
|
||||
sr.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
@endcode
|
||||
|
||||
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));
|
||||
@endcode
|
||||
|
||||
\subsubsection requests-serialization Serialization
|
||||
|
||||
The \c send and \c send_range functions above work with integers
|
||||
e.g. \c int and \c std::string out of the box. To send your own
|
||||
data type defined the \c to_bulk function like this
|
||||
|
||||
@code
|
||||
// Example struct.
|
||||
struct mystruct {
|
||||
// ...
|
||||
};
|
||||
|
||||
void to_bulk(std::string& to, mystruct const& obj)
|
||||
{
|
||||
// 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
|
||||
{ {"key1", {...}}
|
||||
, {"key2", {...}}
|
||||
, {"key3", {...}}};
|
||||
|
||||
db.send_range(command::hset, "key", map);
|
||||
@endcode
|
||||
|
||||
It is quite common to store json string in Redis for example.
|
||||
|
||||
\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
|
||||
(https://redis.io/commands). For example
|
||||
|
||||
Command | RESP3 type | Documentation
|
||||
---------|-------------------------------------|--------------
|
||||
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
|
||||
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. 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`, `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
|
||||
|
||||
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, dbuffer, adapt());
|
||||
|
||||
// Read in a std::string e.g. get.
|
||||
std::string 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, dbuffer, adapt(number));
|
||||
|
||||
// Read in a std::set e.g. smembers.
|
||||
std::set<T, U> 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, dbuffer, adapt(map));
|
||||
|
||||
// Read in a std::unordered_map e.g. hgetall.
|
||||
std::unordered_map<T, U> 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, dbuffer, adapt(vec));
|
||||
@endcode
|
||||
|
||||
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
|
||||
|
||||
It is not uncommon for apps to access keys that do not exist or
|
||||
that have already expired in the Redis server, to deal with these
|
||||
cases Aedis provides support for \c boost::optional. To use it,
|
||||
wrap your type around \c boost::optional like this
|
||||
|
||||
@code
|
||||
boost::optional<std::unordered_map<T, U>> umap;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(umap));
|
||||
@endcode
|
||||
|
||||
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
|
||||
|
||||
There are cases where Redis returns aggregates that
|
||||
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 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.
|
||||
|
||||
@code
|
||||
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>>;
|
||||
@endcode
|
||||
|
||||
Transactions are another example where this feature is useful, for
|
||||
example, the response to the transaction below
|
||||
|
||||
@code
|
||||
db.send(command::multi);
|
||||
db.send(command::get, "key1");
|
||||
db.send(command::lrange, "key2", 0, -1);
|
||||
db.send(command::hgetall, "key3");
|
||||
db.send(command::exec);
|
||||
@endcode
|
||||
|
||||
can be read in the following way
|
||||
|
||||
@code
|
||||
std::tuple<
|
||||
boost::optional<std::string>, // Response to get
|
||||
boost::optional<std::vector<std::string>>, // Response to lrange
|
||||
boost::optional<std::map<std::string, std::string>> // Response to hgetall
|
||||
> trans;
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore multi
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore get
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // Ignore hgetall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(trans));
|
||||
@endcode
|
||||
|
||||
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
|
||||
|
||||
As mentioned in \ref requests-serialization, it is common for
|
||||
users to serialized data before sending it to Redis e.g. json
|
||||
strings, for example
|
||||
|
||||
@code
|
||||
sr.push(command::set, "key", "{"Server": "Redis"}"); // Unquoted for readability.
|
||||
sr.push(command::get, "key")
|
||||
@endcode
|
||||
|
||||
For performance and convenience reasons, we may want to avoid
|
||||
receiving the response to the \c get command above as a string
|
||||
just to convert it later to a e.g. deserialized json. To support
|
||||
this, Aedis calls a user defined \c from_string function while
|
||||
parsing the response. In simple terms, define your type
|
||||
|
||||
@code
|
||||
struct mystruct {
|
||||
// struct fields.
|
||||
};
|
||||
@endcode
|
||||
|
||||
and deserialize it from a string in a function \c from_string with
|
||||
the following signature
|
||||
|
||||
@code
|
||||
void from_string(mystruct& obj, char const* p, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
// Deserializes p into obj.
|
||||
}
|
||||
@endcode
|
||||
|
||||
After that, you can start receiving data efficiently in the desired
|
||||
types e.g. \c mystruct, \c std::map<std::string, mystruct> etc.
|
||||
|
||||
\subsubsection gen-case The general case
|
||||
|
||||
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
|
||||
will result in error.
|
||||
@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
|
||||
type, that is the most general form of an element in a response,
|
||||
be it a simple RESP3 type or an aggregate. It is defined like this
|
||||
|
||||
@code
|
||||
template <class String>
|
||||
struct node {
|
||||
// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
|
||||
// The number of elements of an aggregate (or 1 for simple data).
|
||||
std::size_t aggregate_size;
|
||||
|
||||
// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
// The actual data. For aggregate types this is always empty.
|
||||
String value;
|
||||
};
|
||||
@endcode
|
||||
|
||||
Any response to a Redis command can be received in a \c
|
||||
std::vector<node<std::string>>. The vector can be seen as a
|
||||
pre-order view of the response tree
|
||||
(https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
Using it is no different that using other types
|
||||
|
||||
@code
|
||||
// Receives any RESP3 simple data type.
|
||||
node<std::string> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
// Receives any RESP3 simple or aggregate data type.
|
||||
std::vector<node<std::string>> resp;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
@endcode
|
||||
|
||||
For example, suppose we want to retrieve a hash data structure
|
||||
from Redis with \c hgetall, some of the options are
|
||||
|
||||
@li \c std::vector<node<std::string>: Works always.
|
||||
@li \c std::vector<std::string>: Efficient and flat, all elements as string.
|
||||
@li \c std::map<std::string, std::string>: Efficient if you need the data as a \c std::map
|
||||
@li \c std::map<U, V>: Efficient if you are storing serialized data. Avoids temporaries and requires \c from_string for \c U and \c V.
|
||||
|
||||
In addition to the above users can also use unordered versions of the containers. The same reasoning also applies to sets e.g. \c smembers.
|
||||
|
||||
\subsubsection low-level-adapters Adapters
|
||||
|
||||
Users that are not satisfied with any of the options above can
|
||||
write their own adapters very easily. For example, the adapter below
|
||||
can be used to print incoming data to the screen.
|
||||
|
||||
@code
|
||||
auto adapter = [](resp3::node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
{
|
||||
std::cout << nd << std::endl;
|
||||
};
|
||||
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapter);
|
||||
@endcode
|
||||
|
||||
See more in the \ref examples section.
|
||||
|
||||
\section high-level-api High-level API
|
||||
|
||||
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 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).
|
||||
|
||||
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_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){ ... });
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
@endcode
|
||||
|
||||
Users are concerned only with the implementation of the
|
||||
receiver. For example
|
||||
|
||||
@code
|
||||
// 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
|
||||
|
||||
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)
|
||||
{
|
||||
db.send(command::ping, "O rato roeu a roupa do rei de Roma");
|
||||
db.send(command::incr, "counter");
|
||||
db.send(command::set, "key", "Três pratos de trigo para três tigres");
|
||||
db.send(command::get, "key");
|
||||
...
|
||||
}
|
||||
@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. 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
|
||||
|
||||
\b Basics: Focuses on small examples that show basic usage of
|
||||
the library.
|
||||
To better fix what has been said above, users should have a look at some simple examples.
|
||||
|
||||
- intro.cpp: A good starting point. Some commands are sent to the
|
||||
Redis server and the responses are printed to screen.
|
||||
\b Low \b level \b API (sync)
|
||||
|
||||
- transaction.cpp: Shows how to read the responses to a trasaction
|
||||
efficiently. See also https://redis.io/topics/transactions.
|
||||
@li intro_sync.cpp: Synchronous API usage example.
|
||||
@li serialization_sync.cpp: Shows how serialize your own types.
|
||||
|
||||
- multipurpose_response.cpp: Shows how to read any responses to
|
||||
Redis commands, including nested aggegagtes.
|
||||
\b Low \b level \b API (async-coroutine)
|
||||
|
||||
- subscriber.cpp: Shows how channel subscription works at a low
|
||||
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.
|
||||
|
||||
- sync.cpp: Shows hot to use the Aedis synchronous api.
|
||||
\b High \b level \b API (async only)
|
||||
|
||||
- key_expiration.cpp: Shows how to use \c std::optional to deal
|
||||
with keys that may have expired or do not exist.
|
||||
@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 STL \b Containers: Many of the Redis data structures can be
|
||||
directly translated in to STL containers, below you will find some
|
||||
example code. For a list of Redis data types see
|
||||
https://redis.io/topics/data-types.
|
||||
\b Asynchronous \b Servers (high-level API)
|
||||
|
||||
- hashes.cpp: Shows how to read Redis hashes in a \c std::map, \c
|
||||
std::unordered_map and \c std::vector.
|
||||
|
||||
- lists.cpp: Shows how to read Redis lists in \c std::list,
|
||||
\c std::deque, \c std::vector. It also illustrates basic serialization.
|
||||
|
||||
- sets.cpp: Shows how to read Redis sets in a \c std::set, \c
|
||||
std::unordered_set and \c std::vector.
|
||||
|
||||
\b Customization \b points: Shows how de/serialize user types
|
||||
avoiding copies. This is particularly useful for low latency
|
||||
applications that want to avoid unneeded copies, for examples when
|
||||
storing json strings in Redis keys.
|
||||
|
||||
- serialization.cpp: Shows how to de/serialize your own
|
||||
non-aggregate data-structures.
|
||||
|
||||
- response_adapter.cpp: Customization point for users that want to
|
||||
de/serialize their own data-structures like containers for example.
|
||||
|
||||
\b Asynchronous \b servers: Contains some non-trivial examples
|
||||
servers that interact with users and Redis asynchronously over
|
||||
long lasting connections using a higher level API.
|
||||
|
||||
- multipurpose_client.cpp: Shows how to use and experimental high
|
||||
level redis client that keeps a long lasting connections to a
|
||||
redis server. This is the starting point for the next examples.
|
||||
|
||||
- echo_server.cpp: Shows the basic principles behind asynchronous
|
||||
communication with a database in an asynchronous server. In this
|
||||
case, the server is a proxy between the user and Redis.
|
||||
|
||||
- chat_room.cpp: Shows how to build a scalable chat room that
|
||||
scales to millions of users.
|
||||
@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
|
||||
|
||||
@@ -99,43 +585,53 @@
|
||||
|
||||
- Boost 1.78 or greater.
|
||||
- Unix Shell and Make.
|
||||
- Compiler with C++20 coroutine support e.g. GCC 10 or greater.
|
||||
- C++14. Some examples require C++20 with coroutine support.
|
||||
- Redis server.
|
||||
|
||||
|
||||
Some examples will also require interaction with
|
||||
|
||||
- redis-cli: used in one example.
|
||||
- redis-cli: Used in one example.
|
||||
- Redis Sentinel Server: used in some examples.
|
||||
|
||||
Aedis has been tested with the following compilers
|
||||
|
||||
- Tested with gcc: 7.5.0, 8.4.0, 9.3.0, 10.3.0.
|
||||
- Tested with clang: 11.0.0, 10.0.0, 9.0.1, 8.0.1, 7.0.1.
|
||||
|
||||
\subsection Installation
|
||||
|
||||
Start by downloading and configuring the library
|
||||
|
||||
|
||||
The first thing to do is to download and unpack Aedis
|
||||
|
||||
```
|
||||
# Download the libray on github.
|
||||
$ wget https://github.com/mzimbres/aedis/release-path # TODO
|
||||
# Download the latest release on github
|
||||
$ wget https://github.com/mzimbres/aedis/releases
|
||||
|
||||
# Uncompress the tarball and cd into the dir
|
||||
$ tar -xzvf aedis-1.0.0.tar.gz && cd aedis-1.0.0
|
||||
|
||||
# Run configure with appropriate C++ flags and your boost
|
||||
# installation, for example # You may also have to use
|
||||
# -Wno-subobject-linkage on gcc.
|
||||
$ CXXFLAGS="-std=c++20 -fcoroutines -g -Wall"\
|
||||
./configure --prefix=/opt/aedis-1.0.0 --with-boost=/opt/boost_1_78_0 --with-boost-libdir=/opt/boost_1_78_0/lib
|
||||
|
||||
```
|
||||
|
||||
To install the library run
|
||||
$ tar -xzvf aedis-version.tar.gz
|
||||
```
|
||||
|
||||
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
|
||||
include directories in your project, otherwise run
|
||||
|
||||
```
|
||||
# See configure --help for all options.
|
||||
$ ./configure --prefix=/opt/aedis-version --with-boost=/opt/boost_1_78_0
|
||||
|
||||
# Install Aedis in the path specified in --prefix
|
||||
$ sudo make install
|
||||
|
||||
```
|
||||
|
||||
At this point you can start using Aedis. To build the examples and
|
||||
test you can also run
|
||||
and include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
```
|
||||
|
||||
in exactly one source file in your applications. At this point you
|
||||
can start using Aedis. To build the examples and run the tests run
|
||||
|
||||
```
|
||||
# Build aedis examples.
|
||||
@@ -144,18 +640,7 @@
|
||||
# Test aedis in your machine.
|
||||
$ make check
|
||||
```
|
||||
|
||||
Finally you will have to include the following header
|
||||
|
||||
```cpp
|
||||
#include <aedis/src.hpp>
|
||||
```
|
||||
in exactly one source file in your applications.
|
||||
|
||||
Windows users can use aedis by either adding the project root
|
||||
directory to their include path or manually copying to another
|
||||
location.
|
||||
|
||||
|
||||
\subsection Developers
|
||||
|
||||
To generate the build system run
|
||||
@@ -164,20 +649,199 @@
|
||||
$ autoreconf -i
|
||||
```
|
||||
|
||||
After that you will have a config in the project dir that you can
|
||||
run as explained above, for example, to use a compiler other that
|
||||
the system compiler use
|
||||
After that you will have a configure script
|
||||
that you can run as explained above, for example, to use a
|
||||
compiler other that the system compiler run
|
||||
|
||||
```
|
||||
CC=/opt/gcc-10.2.0/bin/gcc-10.2.0\
|
||||
CXX=/opt/gcc-10.2.0/bin/g++-10.2.0\
|
||||
CXXFLAGS="-std=c++20 -fcoroutines -g -Wall -Wno-subobject-linkage -Werror" ./configure ...
|
||||
$ CC=/opt/gcc-10.2.0/bin/gcc-10.2.0 CXX=/opt/gcc-10.2.0/bin/g++-10.2.0 CXXFLAGS="-g -Wall -Werror" ./configure ...
|
||||
$ 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,16 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: Remove this.
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace net = boost::asio;
|
||||
}
|
||||
|
||||
701
aedis/generic/client.hpp
Normal file
701
aedis/generic/client.hpp
Normal file
@@ -0,0 +1,701 @@
|
||||
/* 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_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/resp3/node.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
#include <aedis/generic/detail/client_ops.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace generic {
|
||||
|
||||
/** \brief A high level Redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* 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:
|
||||
/// 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, 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)}
|
||||
{
|
||||
if (cfg.idle_timeout < std::chrono::seconds{2})
|
||||
cfg.idle_timeout = std::chrono::seconds{2};
|
||||
}
|
||||
|
||||
/// Returns the executor.
|
||||
auto get_executor() {return read_timer_.get_executor();}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* 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();
|
||||
|
||||
auto const before = requests_.size();
|
||||
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(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto const before = requests_.size();
|
||||
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(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto const before = requests_.size();
|
||||
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(std::make_pair(cmd, d));
|
||||
++info_.back().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
wait_write_timer_.cancel_one();
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
send_range2(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Adds a command to the output command queue.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
send_range2(cmd, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Starts communication with the Redis server asynchronously.
|
||||
*
|
||||
* This function performs the following steps
|
||||
*
|
||||
* @li Resolves the Redis host as of \c async_resolve with the
|
||||
* timeout passed in client::config::resolve_timeout.
|
||||
*
|
||||
* @li Connects to one of the endpoints returned by the resolve
|
||||
* operation with the timeout passed in client::config::connect_timeout.
|
||||
*
|
||||
* @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
|
||||
* 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 CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_run(
|
||||
boost::string_view host = "127.0.0.1",
|
||||
boost::string_view port = "6379",
|
||||
CompletionToken token = CompletionToken{})
|
||||
{
|
||||
host_ = host;
|
||||
port_ = port;
|
||||
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(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:
|
||||
using command_info_type = std::pair<Command, std::size_t>;
|
||||
using time_point_type = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
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;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Erases the commands that were lost as well.
|
||||
commands_.erase(
|
||||
std::begin(commands_),
|
||||
std::begin(commands_) + info_.front().cmds);
|
||||
|
||||
info_.front().cmds = 0;
|
||||
|
||||
// Do not erase the info_ front as we will use it below.
|
||||
// info_.erase(std::begin(info_));
|
||||
}
|
||||
|
||||
// Code below will add a hello to the front of the request and
|
||||
// update info_ and commands_ accordingly.
|
||||
|
||||
auto const old_size = requests_.size();
|
||||
sr_.push(Command::hello, 3);
|
||||
auto const hello_size = requests_.size() - old_size;;
|
||||
|
||||
// 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_));
|
||||
|
||||
// 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 (info_.empty()) {
|
||||
info_.push_back({});
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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 written.
|
||||
bool on_cmd(command_info_type)
|
||||
{
|
||||
BOOST_ASSERT(!info_.empty());
|
||||
BOOST_ASSERT(!commands_.empty());
|
||||
|
||||
commands_.erase(std::begin(commands_));
|
||||
|
||||
if (--info_.front().cmds != 0)
|
||||
return false;
|
||||
|
||||
info_.erase(std::begin(info_));
|
||||
|
||||
return !info_.empty();
|
||||
}
|
||||
|
||||
// Resolves the address passed in async_run and store the results
|
||||
// in endpoints_.
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
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)
|
||||
>(detail::write_op<client>{this}, token, write_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
writer(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(detail::writer_op<client>{this}, token, wait_write_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken = default_completion_token_type>
|
||||
auto
|
||||
async_init(CompletionToken&& token = default_completion_token_type{})
|
||||
{
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code)
|
||||
>(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
|
||||
554
aedis/generic/detail/client_ops.hpp
Normal file
554
aedis/generic/detail/client_ops.hpp
Normal file
@@ -0,0 +1,554 @@
|
||||
/* 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_CLIENT_OPS_HPP
|
||||
#define AEDIS_GENERIC_CLIENT_OPS_HPP
|
||||
|
||||
#include <array>
|
||||
|
||||
#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 Command>
|
||||
struct ping_after_op {
|
||||
Client* cli;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
template <class Self>
|
||||
void
|
||||
operator()(Self& self, boost::system::error_code ec = {})
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 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>
|
||||
struct wait_for_data_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)
|
||||
{
|
||||
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_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:
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
boost::asio::coroutine coro;
|
||||
|
||||
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)
|
||||
{
|
||||
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::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();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
yield cli->wait_write_timer_.async_wait(std::move(self));
|
||||
|
||||
if (!cli->socket_->is_open()) {
|
||||
self.complete(error::write_stop_requested);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Client>
|
||||
struct read_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 = {}
|
||||
, 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)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
reenter (coro) for (;;)
|
||||
{
|
||||
if (cli->read_buffer_.empty()) {
|
||||
yield cli->async_wait_for_data(std::move(self));
|
||||
if (ec) {
|
||||
cli->on_reader_exit();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
cli->last_data_ = std::chrono::steady_clock::now();
|
||||
|
||||
yield cli->async_read(std::move(self));
|
||||
if (ec) {
|
||||
cli->on_reader_exit();
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#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
|
||||
203
aedis/generic/serializer.hpp
Normal file
203
aedis/generic/serializer.hpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/* 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_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 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.
|
||||
*
|
||||
* Example
|
||||
*
|
||||
* @code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer(request);
|
||||
* sr.push(command::hello, 3);
|
||||
* sr.push(command::flushall);
|
||||
* sr.push(command::ping);
|
||||
* sr.push(command::incr, "key");
|
||||
* sr.push(command::quit);
|
||||
* co_await async_write(socket, buffer(request));
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type e.g \c std::string.
|
||||
*
|
||||
* \remarks Non-string types will be converted to string by using \c
|
||||
* to_bulk, which must be made available over ADL.
|
||||
*/
|
||||
template <class Storage>
|
||||
class serializer {
|
||||
private:
|
||||
Storage* request_;
|
||||
|
||||
public:
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param storage The underlying storage object i.e. where the
|
||||
* request is to be stored.
|
||||
*/
|
||||
serializer(Storage& storage) : request_(&storage) {}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer<command>(request);
|
||||
* sr.push(command::set, "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with value "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \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_, type::array, 1 + pack_size);
|
||||
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, make_tuple(args...));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that have a key and have a
|
||||
* dynamic range of arguments. For example
|
||||
*
|
||||
* @code
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range2(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* @endcode
|
||||
*
|
||||
* \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.
|
||||
*/
|
||||
template <class Command, class Key, class ForwardIterator>
|
||||
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_, type::array, 2 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
resp3::add_bulk(*request_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* 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
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push(command::subscribe, std::cbegin(channels), std::cedn(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Command, class ForwardIterator>
|
||||
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_, type::array, 1 + size * distance);
|
||||
resp3::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
resp3::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, key, begin(range), end(range));
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* Equivalent to the overload taking a range (i.e. send_range2).
|
||||
*/
|
||||
template <class Command, class Range>
|
||||
void push_range(Command cmd, Range const& range)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
push_range2(cmd, begin(range), end(range));
|
||||
}
|
||||
};
|
||||
|
||||
/** \brief Creates a serializer.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
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,27 +1,26 @@
|
||||
/* 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>
|
||||
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
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
|
||||
@@ -432,40 +431,27 @@ enum class command {
|
||||
zscore,
|
||||
/// https://redis.io/commands/zunionstore
|
||||
zunionstore,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
/// Invalid 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);
|
||||
|
||||
/** \brief Creates a serializer for a \c std::string.
|
||||
* \ingroup any
|
||||
* \param storage The string.
|
||||
*/
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
resp3::serializer<std::string, command>
|
||||
make_serializer(std::basic_string<CharT, Traits, Allocator>& storage)
|
||||
{
|
||||
return resp3::serializer<std::basic_string<CharT, Traits, Allocator>, command>(storage);
|
||||
}
|
||||
|
||||
} // redis
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_REDIS_COMMAND_HPP
|
||||
|
||||
@@ -1,158 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/redis/command.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
/** \brief A high level redis client.
|
||||
* \ingroup any
|
||||
*
|
||||
* This Redis client keeps a connection to the database open and
|
||||
* uses it for all communication with Redis. For examples on how to
|
||||
* use see the examples chat_room.cpp, echo_server.cpp and redis_client.cpp.
|
||||
*
|
||||
* \remarks This class reuses its internal buffers for requests and
|
||||
* for reading Redis responses. With time it will allocate less and
|
||||
* less.
|
||||
*/
|
||||
class client : public std::enable_shared_from_this<client> {
|
||||
public:
|
||||
/** \brief The extended response adapter type.
|
||||
*
|
||||
* The difference between the adapter and extended_adapter
|
||||
* concepts is that the extended get a command redis::parameter.
|
||||
*/
|
||||
using extented_adapter_type = std::function<void(redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&)>;
|
||||
|
||||
/// The type of the message callback.
|
||||
using on_message_type = std::function<void(std::error_code ec, redis::command)>;
|
||||
|
||||
/// The type of the socket used by the client.
|
||||
//using socket_type = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
using socket_type = net::ip::tcp::socket;
|
||||
|
||||
private:
|
||||
struct request_info {
|
||||
// Request size in bytes.
|
||||
std::size_t size = 0;
|
||||
|
||||
// The number of commands it contains excluding commands that
|
||||
// have push types as responses, see has_push_response.
|
||||
std::size_t cmds = 0;
|
||||
};
|
||||
|
||||
// Requests payload.
|
||||
std::string requests_;
|
||||
|
||||
// The commands contained in the requests.
|
||||
std::queue<redis::command> commands_;
|
||||
|
||||
// Info about the requests.
|
||||
std::queue<request_info> req_info_;
|
||||
|
||||
// The stream.
|
||||
socket_type socket_;
|
||||
|
||||
// Timer used to inform the write coroutine that it can write the
|
||||
// next message in the output queue.
|
||||
net::steady_timer timer_;
|
||||
|
||||
// Response adapter.
|
||||
extented_adapter_type extended_adapter_ = [](redis::command, type, std::size_t, std::size_t, char const*, std::size_t, std::error_code&) {};
|
||||
|
||||
// Message callback.
|
||||
on_message_type on_msg_ = [](std::error_code ec, redis::command) {};
|
||||
|
||||
// Set when the writer coroutine should stop.
|
||||
bool stop_writer_ = false;
|
||||
|
||||
// A coroutine that keeps reading the socket. When a message
|
||||
// arrives it calls on_message.
|
||||
net::awaitable<void> reader();
|
||||
|
||||
// Write coroutine. It is kept suspended until there are messages
|
||||
// to be sent.
|
||||
net::awaitable<void> writer();
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
bool prepare_next();
|
||||
|
||||
public:
|
||||
/** \brief Client constructor.
|
||||
*
|
||||
* Constructos the client from an executor.
|
||||
*
|
||||
* \param ex The executor.
|
||||
*/
|
||||
client(net::any_io_executor ex);
|
||||
|
||||
/// Returns the executor used for I/O with Redis.
|
||||
auto get_executor() {return socket_.get_executor();}
|
||||
|
||||
/** \brief Starts communication with Redis.
|
||||
*
|
||||
* This functions will send the hello command to Redis and spawn
|
||||
* the read and write coroutines.
|
||||
*
|
||||
* \param socket A socket that is connected to redis.
|
||||
*
|
||||
* \returns This function returns an awaitable on which users should \c
|
||||
* co_await. When the communication with Redis is lost the
|
||||
* coroutine will finally co_return.
|
||||
*/
|
||||
net::awaitable<void> engage(socket_type socket);
|
||||
|
||||
/** \brief Adds a command to the command queue.
|
||||
*
|
||||
* \sa serializer.hpp
|
||||
*/
|
||||
template <class... Ts>
|
||||
void send(redis::command cmd, Ts const&... args);
|
||||
|
||||
/// Sets an extended response adapter.
|
||||
void set_extended_adapter(extented_adapter_type adapter);
|
||||
|
||||
/// Sets the message callback;
|
||||
void set_msg_callback(on_message_type on_msg);
|
||||
};
|
||||
|
||||
template <class... Ts>
|
||||
void client::send(redis::command cmd, Ts const&... args)
|
||||
{
|
||||
auto const can_write = prepare_next();
|
||||
|
||||
auto sr = redis::make_serializer(requests_);
|
||||
auto const before = std::size(requests_);
|
||||
sr.push(cmd, args...);
|
||||
auto const after = std::size(requests_);
|
||||
req_info_.front().size += after - before;;
|
||||
|
||||
if (!has_push_response(cmd)) {
|
||||
commands_.emplace(cmd);
|
||||
++req_info_.front().cmds;
|
||||
}
|
||||
|
||||
if (can_write)
|
||||
timer_.cancel_one();
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,200 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/redis/experimental/client.hpp>
|
||||
|
||||
#include <boost/asio/experimental/awaitable_operators.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace experimental {
|
||||
|
||||
client::client(net::any_io_executor ex)
|
||||
: socket_{ex}
|
||||
, timer_{ex}
|
||||
{
|
||||
timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
}
|
||||
|
||||
net::awaitable<void> client::reader()
|
||||
{
|
||||
// Writes and reads continuosly from the socket.
|
||||
for (std::string buffer;;) {
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the writer is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
boost::system::error_code ec;
|
||||
co_await
|
||||
net::async_write(
|
||||
socket_,
|
||||
net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break; // We must await the responses.
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
|
||||
do { // Keeps reading while there are no messages queued waiting to be sent.
|
||||
do { // Consumes the responses to all commands in the request.
|
||||
boost::system::error_code ec;
|
||||
auto const t =
|
||||
co_await async_read_type(socket_, net::dynamic_buffer(buffer),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (t == type::push) {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::unknown, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, redis::command::unknown);
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(commands_.front(), t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
on_msg_(ec, commands_.front());
|
||||
if (ec) { // TODO: Return only on non aedis errors.
|
||||
stop_writer_ = true;
|
||||
timer_.cancel();
|
||||
co_return;
|
||||
}
|
||||
|
||||
commands_.pop();
|
||||
--req_info_.front().cmds;
|
||||
}
|
||||
|
||||
} while (!std::empty(req_info_) && req_info_.front().cmds != 0);
|
||||
|
||||
// We may exit the loop above either because we are done
|
||||
// with the response or because we received a server push
|
||||
// while the queue was empty so we have to check before
|
||||
// poping..
|
||||
if (!std::empty(req_info_))
|
||||
req_info_.pop();
|
||||
|
||||
} while (std::empty(req_info_));
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> client::writer()
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
while (socket_.is_open()) {
|
||||
ec = {};
|
||||
co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
if (stop_writer_)
|
||||
co_return;
|
||||
|
||||
// Notice this coro can get scheduled while the write operation
|
||||
// in the reader is ongoing. so we have to check.
|
||||
while (!std::empty(req_info_) && req_info_.front().size != 0) {
|
||||
assert(!std::empty(requests_));
|
||||
ec = {};
|
||||
co_await net::async_write(
|
||||
socket_, net::buffer(requests_.data(), req_info_.front().size),
|
||||
net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec) {
|
||||
// What should we do here exactly? Closing the socket will
|
||||
// cause the reader coroutine to return so that the engage
|
||||
// coroutine returns to the user.
|
||||
socket_.close();
|
||||
co_return;
|
||||
}
|
||||
|
||||
requests_.erase(0, req_info_.front().size);
|
||||
req_info_.front().size = 0;
|
||||
|
||||
if (req_info_.front().cmds != 0)
|
||||
break;
|
||||
|
||||
req_info_.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool client::prepare_next()
|
||||
{
|
||||
if (std::empty(req_info_)) {
|
||||
req_info_.push({});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req_info_.front().size == 0) {
|
||||
// It has already been written and we are waiting for the
|
||||
// responses.
|
||||
req_info_.push({});
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void client::set_extended_adapter(extented_adapter_type adapter)
|
||||
{
|
||||
extended_adapter_ = adapter;
|
||||
}
|
||||
|
||||
void client::set_msg_callback(on_message_type on_msg)
|
||||
{
|
||||
on_msg_ = on_msg;
|
||||
}
|
||||
|
||||
net::awaitable<void> client::engage(socket_type socket)
|
||||
{
|
||||
using namespace aedis::net::experimental::awaitable_operators;
|
||||
|
||||
socket_ = std::move(socket);
|
||||
|
||||
std::string request;
|
||||
auto sr = redis::make_serializer(request);
|
||||
sr.push(redis::command::hello, 3);
|
||||
|
||||
boost::system::error_code ec;
|
||||
co_await net::async_write(socket_, net::buffer(request), net::redirect_error(net::use_awaitable, ec));
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
std::string buffer;
|
||||
auto adapter = [this](type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec)
|
||||
{extended_adapter_(redis::command::hello, t, aggregate_size, depth, data, size, ec);};
|
||||
|
||||
co_await
|
||||
resp3::async_read(
|
||||
socket_, net::dynamic_buffer(buffer), adapter, net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
on_msg_(ec, redis::command::hello);
|
||||
|
||||
if (ec)
|
||||
co_return;
|
||||
|
||||
co_await (reader() && writer());
|
||||
}
|
||||
|
||||
} // experimental
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
@@ -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/redis/command.hpp>
|
||||
@@ -217,6 +216,7 @@ char const* to_string(command c)
|
||||
"ZSCAN",
|
||||
"ZSCORE",
|
||||
"ZUNIONSTORE",
|
||||
"INVALID",
|
||||
};
|
||||
|
||||
return table[static_cast<int>(c)];
|
||||
|
||||
@@ -1,94 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Creates a void response adapter.
|
||||
\ingroup any
|
||||
|
||||
The adapter returned by this function ignores responses and is
|
||||
useful to avoid wasting time with responses which the user is
|
||||
insterested in.
|
||||
|
||||
Example usage:
|
||||
|
||||
@code
|
||||
co_await async_read(socket, buffer, adapt());
|
||||
@endcode
|
||||
*/
|
||||
inline
|
||||
auto adapt() noexcept
|
||||
{ return response_traits<void>::adapt(); }
|
||||
|
||||
/** \brief Adapts user data to read operations.
|
||||
* \ingroup any
|
||||
*
|
||||
* For example
|
||||
* The following types are supported.
|
||||
*
|
||||
* - Integer data types e.g. `int`, `unsigned`, etc.
|
||||
*
|
||||
* - `std::string`
|
||||
*
|
||||
* We also support the following C++ containers
|
||||
*
|
||||
* - `std::vector<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::deque<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::list<T>`. Can be used with any RESP3 aggregate type.
|
||||
*
|
||||
* - `std::set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::unordered_set<T>`. Can be used with RESP3 set type.
|
||||
*
|
||||
* - `std::map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* - `std::unordered_map<T>`. Can be used with RESP3 hash type.
|
||||
*
|
||||
* All these types can be wrapped in an `std::optional<T>`. This
|
||||
* function also support \c std::tuple to read the response to
|
||||
* tuples. At the moment this feature supports only transactions that
|
||||
* contain simple types or aggregates that don't contain aggregates
|
||||
* themselves (as in most cases).
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code
|
||||
* std::unordered_map<std::string, std::string> cont;
|
||||
* co_await async_read(socket, buffer, adapt(cont));
|
||||
* @endcode
|
||||
*
|
||||
* For a transaction
|
||||
*
|
||||
* @code
|
||||
sr.push(command::multi);
|
||||
sr.push(command::ping, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push_range(command::rpush, ...);
|
||||
sr.push(command::lrange, ...);
|
||||
sr.push(command::incr, ...);
|
||||
sr.push(command::exec);
|
||||
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to a transaction
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
* @endcode
|
||||
*/
|
||||
template<class T>
|
||||
auto adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,382 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
#include <aedis/resp3/serializer.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
typename std::enable_if<std::is_integral<T>::value, void>::type
|
||||
from_string(
|
||||
T& i,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const res = std::from_chars(data, data + data_size, i);
|
||||
if (res.ec != std::errc())
|
||||
ec = std::make_error_code(res.ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class Allocator>
|
||||
void
|
||||
from_string(
|
||||
std::basic_string<CharT, Traits, Allocator>& s,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
s.assign(data, data_size);
|
||||
}
|
||||
|
||||
void set_on_resp3_error(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
case type::null: ec = adapter::error::null; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// For optional responses.
|
||||
void set_on_resp3_error2(type t, std::error_code& ec)
|
||||
{
|
||||
switch (t) {
|
||||
case type::simple_error: ec = adapter::error::simple_error; return;
|
||||
case type::blob_error: ec = adapter::error::blob_error; return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapter that ignores responses.
|
||||
struct ignore {
|
||||
void
|
||||
operator()(
|
||||
type, std::size_t, std::size_t, char const*, std::size_t,
|
||||
std::error_code&) { }
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class general {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
general(Container* c = nullptr): result_(c) {}
|
||||
|
||||
/** @brief Function called by the parser when new data has been processed.
|
||||
*
|
||||
* Users who what to customize their response types are required to derive
|
||||
* from this class and override this function, see examples.
|
||||
*
|
||||
* \param t The RESP3 type of the data.
|
||||
*
|
||||
* \param n When t is an aggregate data type this will contain its size
|
||||
* (see also element_multiplicity) for simple data types this is always 1.
|
||||
*
|
||||
* \param depth The element depth in the tree.
|
||||
*
|
||||
* \param data A pointer to the data.
|
||||
*
|
||||
* \param size The size of data.
|
||||
*/
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->emplace_back(t, aggregate_size, depth, std::string{data, size});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Node>
|
||||
class adapter_node {
|
||||
private:
|
||||
Node* result_;
|
||||
|
||||
public:
|
||||
adapter_node(Node* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code&)
|
||||
{
|
||||
result_->data_type = t;
|
||||
result_->aggregate_size = aggregate_size;
|
||||
result_->depth = depth;
|
||||
result_->data.assign(data, data_size);
|
||||
}
|
||||
};
|
||||
|
||||
// Adapter for RESP3 simple data types.
|
||||
template <class T>
|
||||
class simple {
|
||||
private:
|
||||
T* result_;
|
||||
|
||||
public:
|
||||
simple(T* t = nullptr) : result_(t) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
from_string(*result_, data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class simple_optional {
|
||||
private:
|
||||
std::optional<T>* result_;
|
||||
|
||||
public:
|
||||
simple_optional(std::optional<T>* o = nullptr) : result_(o) {}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error2(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
ec = adapter::error::expects_simple_type;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
if (t == type::null)
|
||||
return;
|
||||
|
||||
if (!result_->has_value())
|
||||
*result_ = T{};
|
||||
|
||||
from_string(result_->value(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
/* A std::vector adapter.
|
||||
*/
|
||||
template <class Container>
|
||||
class vector {
|
||||
private:
|
||||
int i_ = -1;
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
vector(Container* v = nullptr) : result_{v} {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (i_ != -1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const m = element_multiplicity(t);
|
||||
result_->resize(m * aggregate_size);
|
||||
++i_;
|
||||
} else {
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
from_string(result_->at(i_), data, data_size, ec);
|
||||
++i_;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class list {
|
||||
private:
|
||||
Container* result_;
|
||||
|
||||
public:
|
||||
list(Container* ref = nullptr): result_(ref) {}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (is_aggregate(t)) {
|
||||
if (depth != 0) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (depth != 1) {
|
||||
ec = adapter::error::nested_unsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
result_->push_back({});
|
||||
from_string(result_->back(), data, data_size, ec);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class set {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator hint_;
|
||||
|
||||
public:
|
||||
set(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, hint_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::set) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
if (hint_ == std::end(*result_)) {
|
||||
auto const ret = result_->insert(std::move(obj));
|
||||
hint_ = ret.first;
|
||||
} else {
|
||||
hint_ = result_->insert(hint_, std::move(obj));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class Container>
|
||||
class map {
|
||||
private:
|
||||
Container* result_;
|
||||
typename Container::iterator current_;
|
||||
bool on_key_ = true;
|
||||
|
||||
public:
|
||||
map(Container* c = nullptr)
|
||||
: result_(c)
|
||||
, current_(std::end(*c))
|
||||
{}
|
||||
|
||||
void
|
||||
operator()(type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t data_size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
set_on_resp3_error(t, ec);
|
||||
|
||||
if (t == type::map) {
|
||||
assert(depth == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(!is_aggregate(t));
|
||||
assert(depth == 1);
|
||||
assert(aggregate_size == 1);
|
||||
|
||||
if (on_key_) {
|
||||
typename Container::key_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_ = result_->insert(current_, {std::move(obj), {}});
|
||||
} else {
|
||||
typename Container::mapped_type obj;
|
||||
from_string(obj, data, data_size, ec);
|
||||
current_->second = std::move(obj);
|
||||
}
|
||||
|
||||
on_key_ = !on_key_;
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
# include <system_error>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace adapter {
|
||||
|
||||
/** \brief Errors that may occurr when reading a response.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
{
|
||||
/// Expects a simple RESP3 type but got an aggregate.
|
||||
expects_simple_type = 1,
|
||||
|
||||
/// Nested response not supported.
|
||||
nested_unsupported,
|
||||
|
||||
/// Got RESP3 simple error.
|
||||
simple_error,
|
||||
|
||||
/// Got RESP3 blob_error.
|
||||
blob_error,
|
||||
|
||||
/// The tuple used as response has incompatible size.
|
||||
incompatible_tuple_size,
|
||||
|
||||
/// Got RESP3 null type.
|
||||
null
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
/// \todo Fix string lifetime.
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.response_adapter"; }
|
||||
|
||||
// TODO: Move to .ipp
|
||||
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::nested_unsupported: return "Nested responses unsupported.";
|
||||
case error::simple_error: return "Got RESP3 simple-error.";
|
||||
case error::blob_error: return "Got RESP3 blob-error.";
|
||||
case error::incompatible_tuple_size: return "The tuple used as response has incompatible size.";
|
||||
case error::null: return "Got RESP3 null.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& adapter_category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::adapter_category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::adapter_category());
|
||||
}
|
||||
|
||||
} // adapter
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
namespace std {
|
||||
|
||||
template<>
|
||||
struct is_error_code_enum<::aedis::resp3::adapter::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
159
aedis/resp3/compose.hpp
Normal file
159
aedis/resp3/compose.hpp
Normal file
@@ -0,0 +1,159 @@
|
||||
/* 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_RESP3_COMPOSE_HPP
|
||||
#define AEDIS_RESP3_COMPOSE_HPP
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include <boost/hana.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
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_code(type::blob_string);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
to.append(std::cbegin(data), std::cend(data));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
template <class Request, class T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
|
||||
void to_bulk(Request& to, T n)
|
||||
{
|
||||
auto const s = std::to_string(n);
|
||||
to_bulk(to, boost::string_view{s});
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
struct add_bulk_impl {
|
||||
template <class Request>
|
||||
static void add(Request& to, T const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <class U, class V>
|
||||
struct add_bulk_impl<std::pair<U, V>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, std::pair<U, V> const& from)
|
||||
{
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, from.first);
|
||||
to_bulk(to, from.second);
|
||||
}
|
||||
};
|
||||
|
||||
template <class ...Ts>
|
||||
struct add_bulk_impl<boost::hana::tuple<Ts...>> {
|
||||
template <class Request>
|
||||
static void add(Request& to, boost::hana::tuple<Ts...> const& from)
|
||||
{
|
||||
using boost::hana::for_each;
|
||||
|
||||
// Fold expressions is C++17 so we use hana.
|
||||
//(resp3::add_bulk(*request_, args), ...);
|
||||
|
||||
for_each(from, [&](auto const& e) {
|
||||
using namespace aedis::resp3;
|
||||
to_bulk(to, e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** @brief Adds a resp3 header to the request.
|
||||
* @ingroup any
|
||||
*
|
||||
* See mystruct.hpp for an example.
|
||||
*/
|
||||
template <class Request>
|
||||
void add_header(Request& to, type t, std::size_t size)
|
||||
{
|
||||
auto const str = std::to_string(size);
|
||||
|
||||
to += to_code(t);
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += separator;
|
||||
}
|
||||
|
||||
/* Adds a rep3 bulk to the request.
|
||||
*
|
||||
* 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)
|
||||
{
|
||||
detail::add_bulk_impl<T>::add(to, data);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct bulk_counter;
|
||||
|
||||
template <class>
|
||||
struct bulk_counter {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
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,87 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
template <class>
|
||||
struct needs_to_string : std::true_type {};
|
||||
|
||||
template <> struct needs_to_string<std::string> : std::false_type {};
|
||||
template <> struct needs_to_string<std::string_view> : std::false_type {};
|
||||
template <> struct needs_to_string<char const*> : std::false_type {};
|
||||
template <> struct needs_to_string<char*> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char[N]> : std::false_type {};
|
||||
|
||||
template <std::size_t N>
|
||||
struct needs_to_string<char const[N]> : std::false_type {};
|
||||
|
||||
template <class Storage>
|
||||
void add_header(Storage& to, int size)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(size);
|
||||
|
||||
to += "*";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage>
|
||||
void add_bulk(Storage& to, std::string_view data)
|
||||
{
|
||||
// std::string does not support allocators.
|
||||
using std::to_string;
|
||||
auto const str = to_string(std::size(data));
|
||||
|
||||
to += "$";
|
||||
to.append(std::cbegin(str), std::cend(str));
|
||||
to += "\r\n";
|
||||
to += data;
|
||||
to += "\r\n";
|
||||
}
|
||||
|
||||
template <class Storage, class T>
|
||||
void add_bulk(Storage& to, T const& data, typename std::enable_if<needs_to_string<T>::value, bool>::type = false)
|
||||
{
|
||||
using std::to_string;
|
||||
auto const s = to_string(data);
|
||||
add_bulk(to, s);
|
||||
}
|
||||
|
||||
// Overload for pairs.
|
||||
// TODO: Overload for tuples.
|
||||
template <class Storage, class T1, class T2>
|
||||
void add_bulk(Storage& to, std::pair<T1, T2> const& pair)
|
||||
{
|
||||
add_bulk(to, pair.first);
|
||||
add_bulk(to, pair.second);
|
||||
}
|
||||
|
||||
template <class>
|
||||
struct value_type_size {
|
||||
static constexpr auto size = 1U;
|
||||
};
|
||||
|
||||
template <class T, class U>
|
||||
struct value_type_size<std::pair<T, U>> {
|
||||
static constexpr auto size = 2U;
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,10 +1,12 @@
|
||||
/* 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)
|
||||
*/
|
||||
|
||||
#include <boost/spirit/include/qi.hpp>
|
||||
#include <boost/spirit/home/x3.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
|
||||
@@ -12,27 +14,15 @@ namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
type to_type(char c)
|
||||
std::size_t
|
||||
parse_uint(char const* data, std::size_t size, boost::system::error_code& ec)
|
||||
{
|
||||
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;
|
||||
}
|
||||
static constexpr boost::spirit::x3::uint_parser<std::size_t, 10> p{};
|
||||
std::size_t ret;
|
||||
if (!parse(data, data + size, p, ret))
|
||||
ec = error::not_a_number;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
/* 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 <charconv>
|
||||
#include <system_error>
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/error.hpp>
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// Converts a wire-format RESP3 type (char) to a resp3 type.
|
||||
type to_type(char c);
|
||||
std::size_t parse_uint(char const* data, std::size_t size, boost::system::error_code& ec);
|
||||
|
||||
template <class ResponseAdapter>
|
||||
class parser {
|
||||
private:
|
||||
using node_type = node<boost::string_view>;
|
||||
static constexpr std::size_t max_embedded_depth = 5;
|
||||
|
||||
ResponseAdapter adapter_;
|
||||
@@ -54,24 +56,21 @@ public:
|
||||
|
||||
// Returns the number of bytes that have been consumed.
|
||||
std::size_t
|
||||
advance(char const* data, std::size_t n, std::error_code& ec)
|
||||
consume(char const* data, std::size_t n, boost::system::error_code& ec)
|
||||
{
|
||||
if (bulk_ != type::invalid) {
|
||||
n = bulk_length_ + 2;
|
||||
switch (bulk_) {
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
if (bulk_length_ == 0) {
|
||||
sizes_[depth_] = 1;
|
||||
} else {
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
BOOST_ASSERT(bulk_length_ != 0);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
} break;
|
||||
default:
|
||||
{
|
||||
adapter_(bulk_, 1, depth_, data, bulk_length_, ec);
|
||||
adapter_({bulk_, 1, depth_, {data, bulk_length_}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
@@ -83,46 +82,75 @@ public:
|
||||
} else if (sizes_[depth_] != 0) {
|
||||
auto const t = to_type(*data);
|
||||
switch (t) {
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::streamed_string_part:
|
||||
{
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
}
|
||||
|
||||
bulk_ = t;
|
||||
if (bulk_length_ == 0) {
|
||||
adapter_({type::streamed_string_part, 1, depth_, {}}, ec);
|
||||
sizes_[depth_] = 0; // We are done.
|
||||
} else {
|
||||
bulk_ = type::streamed_string_part;
|
||||
}
|
||||
} break;
|
||||
case type::blob_error:
|
||||
case type::verbatim_string:
|
||||
case type::blob_string:
|
||||
{
|
||||
if (*(data + 1) == '?') {
|
||||
// NOTE: This can only be triggered with blob_string.
|
||||
// Trick: A streamed string is read as an aggregate
|
||||
// of infinite lenght. When the streaming is done
|
||||
// the server is supposed to send a part with lenght
|
||||
// the server is supposed to send a part with length
|
||||
// 0.
|
||||
sizes_[++depth_] = (std::numeric_limits<std::size_t>::max)();
|
||||
} else {
|
||||
auto const r =
|
||||
std::from_chars(data + 1, data + n - 2, bulk_length_);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
bulk_length_ = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
bulk_ = type::blob_string;
|
||||
bulk_ = t;
|
||||
}
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::number:
|
||||
case type::doublean:
|
||||
case type::boolean:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (*(data + 1) != 'f' && *(data + 1) != 't') {
|
||||
ec = error::unexpected_bool_value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::doublean:
|
||||
case type::big_number:
|
||||
case type::number:
|
||||
{
|
||||
if (n == 3) {
|
||||
ec = error::empty_field;
|
||||
return 0;
|
||||
}
|
||||
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
--sizes_[depth_];
|
||||
} break;
|
||||
case type::simple_error:
|
||||
case type::simple_string:
|
||||
{
|
||||
adapter_(t, 1, depth_, data + 1, n - 3, ec);
|
||||
adapter_({t, 1, depth_, {data + 1, n - 3}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -130,7 +158,7 @@ public:
|
||||
} break;
|
||||
case type::null:
|
||||
{
|
||||
adapter_(type::null, 1, depth_, nullptr, 0, ec);
|
||||
adapter_({type::null, 1, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -142,14 +170,11 @@ public:
|
||||
case type::attribute:
|
||||
case type::map:
|
||||
{
|
||||
std::size_t l;
|
||||
auto const r = std::from_chars(data + 1, data + n - 2, l);
|
||||
if (r.ec != std::errc()) {
|
||||
ec = error::not_a_number;
|
||||
return 0;
|
||||
}
|
||||
auto const l = parse_uint(data + 1, n - 2, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
adapter_(t, l, depth_, nullptr, 0, ec);
|
||||
adapter_({t, l, depth_, {}}, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -197,3 +222,5 @@ public:
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_PARSER_HPP
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
/* 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_READ_OPS_HPP
|
||||
#define AEDIS_RESP3_READ_OPS_HPP
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/read_until.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
#include <boost/core/ignore_unused.hpp>
|
||||
#include <boost/utility/string_view.hpp>
|
||||
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
struct ignore_response {
|
||||
void operator()(node<boost::string_view>, boost::system::error_code&) { }
|
||||
};
|
||||
|
||||
template <
|
||||
class AsyncReadStream,
|
||||
class DynamicBuffer,
|
||||
@@ -28,7 +36,8 @@ private:
|
||||
DynamicBuffer buf_;
|
||||
parser<ResponseAdapter> parser_;
|
||||
std::size_t consumed_;
|
||||
int start_;
|
||||
std::size_t buffer_size_;
|
||||
boost::asio::coroutine coro_;
|
||||
|
||||
public:
|
||||
parse_op(AsyncReadStream& stream, DynamicBuffer buf, ResponseAdapter adapter)
|
||||
@@ -36,7 +45,6 @@ public:
|
||||
, buf_ {buf}
|
||||
, parser_ {adapter}
|
||||
, consumed_{0}
|
||||
, start_{1}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
@@ -44,15 +52,16 @@ public:
|
||||
, boost::system::error_code ec = {}
|
||||
, std::size_t n = 0)
|
||||
{
|
||||
switch (start_) {
|
||||
for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
case 1:
|
||||
start_ = 0;
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
reenter (coro_) for (;;) {
|
||||
if (parser_.bulk() == type::invalid) {
|
||||
yield
|
||||
boost::asio::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// On a bulk read we can't read until delimiter since the
|
||||
// payload may contain the delimiter itself so we have to
|
||||
// read the whole chunk. However if the bulk blob is small
|
||||
@@ -60,78 +69,47 @@ public:
|
||||
// read), in which case there is no need of initiating
|
||||
// another async op, otherwise we have to read the missing
|
||||
// bytes.
|
||||
if (std::size(buf_) < (parser_.bulk_length() + 2)) {
|
||||
start_ = 0;
|
||||
auto const s = std::size(buf_);
|
||||
auto const l = parser_.bulk_length();
|
||||
auto const to_read = l + 2 - s;
|
||||
buf_.grow(to_read);
|
||||
net::async_read(stream_, buf_.data(s, to_read), net::transfer_all(), std::move(self));
|
||||
return;
|
||||
if (buf_.size() < (parser_.bulk_length() + 2)) {
|
||||
buffer_size_ = buf_.size();
|
||||
buf_.grow(parser_.bulk_length() + 2 - buffer_size_);
|
||||
|
||||
yield
|
||||
boost::asio::async_read(
|
||||
stream_,
|
||||
buf_.data(buffer_size_, parser_.bulk_length() + 2 - buffer_size_),
|
||||
boost::asio::transfer_all(),
|
||||
std::move(self));
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
n = parser_.bulk_length() + 2;
|
||||
BOOST_ASSERT(buf_.size() >= n);
|
||||
}
|
||||
|
||||
n = parser_.advance((char const*)buf_.data(0, n).data(), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
n = parser_.consume((char const*)buf_.data(0, n).data(), n, ec);
|
||||
if (ec) {
|
||||
self.complete(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
buf_.consume(n);
|
||||
consumed_ += n;
|
||||
if (parser_.done()) {
|
||||
self.complete({}, consumed_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use asio::coroutine.
|
||||
template <class AsyncReadStream, class DynamicBuffer>
|
||||
class type_op {
|
||||
private:
|
||||
AsyncReadStream& stream_;
|
||||
DynamicBuffer buf_;
|
||||
|
||||
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)
|
||||
{
|
||||
boost::ignore_unused(n);
|
||||
|
||||
if (ec) {
|
||||
self.complete(ec, type::invalid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::size(buf_) == 0) {
|
||||
net::async_read_until(stream_, buf_, "\r\n", std::move(self));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const* data = (char const*)buf_.data(0, n).data();
|
||||
auto const type = to_type(*data);
|
||||
self.complete(ec, type);
|
||||
return;
|
||||
}
|
||||
};
|
||||
#include <boost/asio/unyield.hpp>
|
||||
|
||||
} // detail
|
||||
} // resp3
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_READ_OPS_HPP
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#pragma once
|
||||
/* 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 <system_error>
|
||||
#ifndef AEDIS_RESP3_ERROR_HPP
|
||||
#define AEDIS_RESP3_ERROR_HPP
|
||||
|
||||
/// \file error.hpp
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief RESP3 parsing errors.
|
||||
/** \brief RESP3 errors.
|
||||
* \ingroup any
|
||||
*/
|
||||
enum class error
|
||||
@@ -15,59 +20,26 @@ 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.
|
||||
unexpected_read_size,
|
||||
|
||||
/// The maximum depth of a nested response was exceeded.
|
||||
exceeeds_max_nested_depth
|
||||
exceeeds_max_nested_depth,
|
||||
|
||||
/// Unexpects bool value.
|
||||
unexpected_bool_value,
|
||||
|
||||
/// Expected field value is empty.
|
||||
empty_field
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : std::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{ return "aedis.resp3"; }
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
case error::exceeeds_max_nested_depth: return "Exceeds the maximum number of nested responses.";
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline
|
||||
std::error_category const& category()
|
||||
{
|
||||
static error_category_impl instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // detail
|
||||
|
||||
/** \brief Converts an error in an std::error_code object.
|
||||
/** \brief Creates a error_code object from an error.
|
||||
* \ingroup any
|
||||
*/
|
||||
inline
|
||||
std::error_code make_error_code(error e)
|
||||
{
|
||||
static detail::error_category_impl const eci{};
|
||||
return std::error_code{static_cast<int>(e), detail::category()};
|
||||
}
|
||||
|
||||
inline
|
||||
std::error_condition make_error_condition(error e)
|
||||
{
|
||||
return std::error_condition(static_cast<int>(e), detail::category());
|
||||
}
|
||||
boost::system::error_code make_error_code(error e);
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -78,3 +50,5 @@ template<>
|
||||
struct is_error_code_enum<::aedis::resp3::error> : std::true_type {};
|
||||
|
||||
} // std
|
||||
|
||||
#endif // AEDIS_RESP3_ERROR_HPP
|
||||
|
||||
49
aedis/resp3/impl/error.ipp
Normal file
49
aedis/resp3/impl/error.ipp
Normal file
@@ -0,0 +1,49 @@
|
||||
/* 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 <boost/assert.hpp>
|
||||
#include <aedis/resp3/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
namespace detail {
|
||||
|
||||
struct error_category_impl : boost::system::error_category {
|
||||
|
||||
char const* name() const noexcept override
|
||||
{
|
||||
return "aedis.resp3";
|
||||
}
|
||||
|
||||
std::string message(int ev) const override
|
||||
{
|
||||
switch(static_cast<error>(ev)) {
|
||||
case error::invalid_type: return "Invalid resp3 type.";
|
||||
case error::not_a_number: return "Can't convert string to number.";
|
||||
case error::unexpected_read_size: return "Unexpected read size.";
|
||||
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: 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()};
|
||||
}
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,67 +0,0 @@
|
||||
/* Copyright (c) 2019 - 2022 Marcelo Zimbres Silva (mzimbres at gmail dot 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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/node.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
std::string to_string(node const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out += in.data;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool operator==(node const& a, node const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& a.data == b.data;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, node const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r)
|
||||
{
|
||||
os << to_string(r);
|
||||
return os;
|
||||
}
|
||||
|
||||
// TODO: Output like in redis-cli.
|
||||
std::string to_string(std::vector<node> const& vec)
|
||||
{
|
||||
if (std::empty(vec))
|
||||
return {};
|
||||
|
||||
auto begin = std::cbegin(vec);
|
||||
std::string res;
|
||||
for (; begin != std::prev(std::cend(vec)); ++begin) {
|
||||
res += to_string(*begin);
|
||||
res += '\n';
|
||||
}
|
||||
|
||||
res += to_string(*begin);
|
||||
return res;
|
||||
}
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,14 +1,12 @@
|
||||
/* 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)
|
||||
*/
|
||||
|
||||
#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>
|
||||
|
||||
@@ -20,10 +20,14 @@ namespace resp3 {
|
||||
*
|
||||
* Redis responses are the pre-order view of the response tree (see
|
||||
* https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR).
|
||||
*
|
||||
* \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 {
|
||||
/// The RESP3 type of the data in this node.
|
||||
type data_type;
|
||||
resp3::type data_type;
|
||||
|
||||
/// The number of elements of an aggregate.
|
||||
std::size_t aggregate_size;
|
||||
@@ -31,38 +35,56 @@ struct node {
|
||||
/// The depth of this node in the response tree.
|
||||
std::size_t depth;
|
||||
|
||||
/// The actual data. For aggregate data types this is always empty.
|
||||
std::string data;
|
||||
/// The actual data. For aggregate types this is usually empty.
|
||||
String value;
|
||||
};
|
||||
|
||||
/** \brief Converts the node to a string.
|
||||
* \ingroup any
|
||||
*
|
||||
* \param obj The node object.
|
||||
* \param in The node object.
|
||||
*/
|
||||
std::string to_string(node const& obj);
|
||||
template <class String>
|
||||
std::string to_string(node<String> const& in)
|
||||
{
|
||||
std::string out;
|
||||
out += std::to_string(in.depth);
|
||||
out += '\t';
|
||||
out += to_string(in.data_type);
|
||||
out += '\t';
|
||||
out += std::to_string(in.aggregate_size);
|
||||
out += '\t';
|
||||
if (!is_aggregate(in.data_type))
|
||||
out.append(in.value.data(), in.value.size());
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/** \brief Compares a node for equality.
|
||||
* \ingroup any
|
||||
*/
|
||||
bool operator==(node const& a, node const& b);
|
||||
template <class String>
|
||||
bool operator==(node<String> const& a, node<String> const& b)
|
||||
{
|
||||
return a.aggregate_size == b.aggregate_size
|
||||
&& a.depth == b.depth
|
||||
&& a.data_type == b.data_type
|
||||
&& 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.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, node const& o);
|
||||
template <class String>
|
||||
std::ostream& operator<<(std::ostream& os, node<String> const& o)
|
||||
{
|
||||
os << to_string(o);
|
||||
return os;
|
||||
}
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::string to_string(std::vector<node> const& vec);
|
||||
|
||||
/** \brief Writes the response to the output stream
|
||||
* \ingroup any
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, std::vector<node> const& r);
|
||||
|
||||
} // resp3
|
||||
} // adapter
|
||||
} // aedis
|
||||
|
||||
#endif // AEDIS_RESP3_NODE_HPP
|
||||
|
||||
@@ -1,36 +1,53 @@
|
||||
/* 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/config.hpp>
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapt.hpp>
|
||||
#include <aedis/resp3/response_traits.hpp>
|
||||
#include <aedis/resp3/detail/parser.hpp>
|
||||
#include <aedis/resp3/detail/read_ops.hpp>
|
||||
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/async_result.hpp>
|
||||
#include <boost/asio/yield.hpp>
|
||||
|
||||
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,
|
||||
@@ -44,12 +61,12 @@ read(
|
||||
ResponseAdapter adapter,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
detail::parser p {adapter};
|
||||
detail::parser<ResponseAdapter> p {adapter};
|
||||
std::size_t n = 0;
|
||||
std::size_t consumed = 0;
|
||||
do {
|
||||
if (p.bulk() == type::invalid) {
|
||||
n = net::read_until(stream, buf, "\r\n", ec);
|
||||
n = boost::asio::read_until(stream, buf, "\r\n", ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -58,12 +75,12 @@ read(
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
auto const s = std::size(buf);
|
||||
auto const s = buf.size();
|
||||
auto const l = p.bulk_length();
|
||||
if (s < (l + 2)) {
|
||||
auto const to_read = l + 2 - s;
|
||||
buf.grow(to_read);
|
||||
n = net::read(stream, buf.data(s, to_read), ec);
|
||||
n = boost::asio::read(stream, buf.data(s, to_read), ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -74,9 +91,8 @@ read(
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto const* data = (char const*) buf.data(0, n).data();
|
||||
n = p.advance(data, n, ec);
|
||||
n = p.consume(data, n, ec);
|
||||
if (ec)
|
||||
return 0;
|
||||
|
||||
@@ -87,27 +103,20 @@ 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,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type>
|
||||
class ResponseAdapter = detail::ignore_response>
|
||||
std::size_t
|
||||
read(
|
||||
SyncReadStream& stream,
|
||||
DynamicBuffer buf,
|
||||
ResponseAdapter adapter = adapt())
|
||||
ResponseAdapter adapter = ResponseAdapter{})
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
auto const n = resp3::read(stream, buf, adapter, ec);
|
||||
@@ -118,40 +127,58 @@ 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,
|
||||
class DynamicBuffer,
|
||||
class ResponseAdapter = response_traits<void>::adapter_type,
|
||||
class CompletionToken = net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
class ResponseAdapter = detail::ignore_response,
|
||||
class CompletionToken = boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
ResponseAdapter adapter = adapt(),
|
||||
ResponseAdapter adapter = ResponseAdapter{},
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
boost::asio::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::async_compose
|
||||
return boost::asio::async_compose
|
||||
< CompletionToken
|
||||
, void(boost::system::error_code, std::size_t)
|
||||
>(detail::parse_op<AsyncReadStream, DynamicBuffer, ResponseAdapter> {stream, buffer, adapter},
|
||||
@@ -159,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 =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>
|
||||
>
|
||||
auto async_read_type(
|
||||
AsyncReadStream& stream,
|
||||
DynamicBuffer buffer,
|
||||
CompletionToken&& token =
|
||||
net::default_completion_token_t<typename AsyncReadStream::executor_type>{})
|
||||
{
|
||||
return net::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,229 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <unordered_set>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <charconv>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
|
||||
#include <boost/mp11.hpp>
|
||||
|
||||
#include <aedis/resp3/type.hpp>
|
||||
#include <aedis/resp3/adapter/detail/adapters.hpp>
|
||||
#include <aedis/resp3/adapter/error.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** \brief Traits class for response objects.
|
||||
* \ingroup any
|
||||
*/
|
||||
template <class T>
|
||||
struct response_traits
|
||||
{
|
||||
/// The response type.
|
||||
using response_type = T;
|
||||
|
||||
/// The adapter type.
|
||||
using adapter_type = adapter::detail::simple<response_type>;
|
||||
|
||||
/// Returns an adapter for the reponse r
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
/// Template typedef for response_traits.
|
||||
template <class T>
|
||||
using response_traits_t = typename response_traits<T>::adapter_type;
|
||||
|
||||
template <class T>
|
||||
struct response_traits<std::optional<T>>
|
||||
{
|
||||
using response_type = std::optional<T>;
|
||||
using adapter_type = adapter::detail::simple_optional<typename response_type::value_type>;
|
||||
static auto adapt(response_type& i) noexcept { return adapter_type{&i}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::vector<T, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<T, Allocator>;
|
||||
using adapter_type = adapter::detail::vector<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<node>
|
||||
{
|
||||
using response_type = node;
|
||||
using adapter_type = adapter::detail::adapter_node<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Allocator>
|
||||
struct response_traits<std::vector<node, Allocator>>
|
||||
{
|
||||
using response_type = std::vector<node, Allocator>;
|
||||
using adapter_type = adapter::detail::general<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::list<T, Allocator>>
|
||||
{
|
||||
using response_type = std::list<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class T, class Allocator>
|
||||
struct response_traits<std::deque<T, Allocator>>
|
||||
{
|
||||
using response_type = std::deque<T, Allocator>;
|
||||
using adapter_type = adapter::detail::list<response_type>;
|
||||
static auto adapt(response_type& v) noexcept { return adapter_type{&v}; }
|
||||
};
|
||||
|
||||
template <class Key, class Compare, class Allocator>
|
||||
struct response_traits<std::set<Key, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::set<Key, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_set<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_set<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::set<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class T, class Compare, class Allocator>
|
||||
struct response_traits<std::map<Key, T, Compare, Allocator>>
|
||||
{
|
||||
using response_type = std::map<Key, T, Compare, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <class Key, class Hash, class KeyEqual, class Allocator>
|
||||
struct response_traits<std::unordered_map<Key, Hash, KeyEqual, Allocator>>
|
||||
{
|
||||
using response_type = std::unordered_map<Key, Hash, KeyEqual, Allocator>;
|
||||
using adapter_type = adapter::detail::map<response_type>;
|
||||
static auto adapt(response_type& s) noexcept { return adapter_type{&s}; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct response_traits<void>
|
||||
{
|
||||
using response_type = void;
|
||||
using adapter_type = adapter::detail::ignore;
|
||||
static auto adapt() noexcept { return adapter_type{}; }
|
||||
};
|
||||
|
||||
namespace adapter {
|
||||
namespace detail {
|
||||
|
||||
// Duplicated here to avoid circular include dependency.
|
||||
template<class T>
|
||||
auto internal_adapt(T& t) noexcept
|
||||
{ return response_traits<T>::adapt(t); }
|
||||
|
||||
template <std::size_t N>
|
||||
struct assigner {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[N].template emplace<N>(internal_adapt(std::get<N>(from)));
|
||||
assigner<N - 1>::assign(dest, from);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct assigner<0> {
|
||||
template <class T1, class T2>
|
||||
static void assign(T1& dest, T2& from)
|
||||
{
|
||||
dest[0] = internal_adapt(std::get<0>(from));
|
||||
}
|
||||
};
|
||||
|
||||
template <class Tuple>
|
||||
class flat_transaction_adapter {
|
||||
private:
|
||||
using variant_type =
|
||||
boost::mp11::mp_rename<boost::mp11::mp_transform<response_traits_t, Tuple>, std::variant>;
|
||||
|
||||
std::size_t i_ = 0;
|
||||
std::size_t aggregate_size_ = 0;
|
||||
std::array<variant_type, std::tuple_size<Tuple>::value> adapters_;
|
||||
|
||||
public:
|
||||
flat_transaction_adapter(Tuple* r)
|
||||
{ assigner<std::tuple_size<Tuple>::value - 1>::assign(adapters_, *r); }
|
||||
|
||||
void count(type t, std::size_t aggregate_size, std::size_t depth)
|
||||
{
|
||||
if (depth == 1) {
|
||||
if (is_aggregate(t))
|
||||
aggregate_size_ = aggregate_size;
|
||||
else
|
||||
++i_;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (--aggregate_size_ == 0)
|
||||
++i_;
|
||||
}
|
||||
|
||||
void
|
||||
operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
if (depth == 0) {
|
||||
if (aggregate_size != std::tuple_size<Tuple>::value) {
|
||||
ec = adapter::error::incompatible_tuple_size;
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::visit([&](auto& arg){arg(t, aggregate_size, depth, data, size, ec);}, adapters_[i_]);
|
||||
count(t, aggregate_size, depth);
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
} // adapter
|
||||
|
||||
// The adapter of responses to transactions, move it to its own header?
|
||||
template <class... Ts>
|
||||
struct response_traits<std::tuple<Ts...>>
|
||||
{
|
||||
using response_type = std::tuple<Ts...>;
|
||||
using adapter_type = adapter::detail::flat_transaction_adapter<response_type>;
|
||||
static auto adapt(response_type& r) noexcept { return adapter_type{&r}; }
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -1,157 +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/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <aedis/resp3/detail/composer.hpp>
|
||||
|
||||
namespace aedis {
|
||||
namespace resp3 {
|
||||
|
||||
/** @brief Creates a Redis request 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
|
||||
*
|
||||
* @code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer(request);
|
||||
* sr.push(command::hello, 3);
|
||||
* sr.push(command::flushall);
|
||||
* sr.push(command::ping);
|
||||
* sr.push(command::incr, "key");
|
||||
* sr.push(command::quit);
|
||||
* co_await async_write(socket, buffer(request));
|
||||
* @endcode
|
||||
*
|
||||
* \tparam Storage The storage type. This is currently a \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.
|
||||
*/
|
||||
|
||||
// NOTE: Consider adding an overload for containers.
|
||||
//
|
||||
// TODO: Should we detect any std::pair or tuple 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 Command>
|
||||
class serializer {
|
||||
private:
|
||||
Storage* request_;
|
||||
|
||||
public:
|
||||
/** \brief Constructor
|
||||
*
|
||||
* \param storage Object where the serialized request will be
|
||||
* stored.
|
||||
*/
|
||||
serializer(Storage& storage) : request_(&storage) {}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* For example
|
||||
*
|
||||
* \code
|
||||
* std::string request;
|
||||
* auto sr = make_serializer<command>(request);
|
||||
* sr.push(command::set, "key", "some string", "EX", "2");
|
||||
* \endcode
|
||||
*
|
||||
* will add the \c set command with payload "some string" and an
|
||||
* expiration of 2 seconds.
|
||||
*
|
||||
* \param cmd The redis command.
|
||||
* \param args The arguments of the Redis command.
|
||||
*
|
||||
*/
|
||||
template <class... Ts>
|
||||
void push(Command cmd, Ts const&... args)
|
||||
{
|
||||
auto constexpr pack_size = sizeof...(Ts);
|
||||
detail::add_header(*request_, 1 + pack_size);
|
||||
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
(detail::add_bulk(*request_, args), ...);
|
||||
}
|
||||
|
||||
/** @brief Appends a new command to the end of the request.
|
||||
*
|
||||
* This overload is useful for commands that require a key. For example
|
||||
*
|
||||
* \code{.cpp}
|
||||
* std::map<std::string, std::string> map
|
||||
* { {"key1", "value1"}
|
||||
* , {"key2", "value2"}
|
||||
* , {"key3", "value3"}
|
||||
* };
|
||||
*
|
||||
* request req;
|
||||
* req.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param key The key the Redis command refers to.
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class Key, class ForwardIterator>
|
||||
void push_range(Command cmd, Key const& key, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 2 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
detail::add_bulk(*request_, key);
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
|
||||
/** @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
|
||||
*
|
||||
* \code
|
||||
* std::set<std::string> channels
|
||||
* { "channel1" , "channel2" , "channel3" }
|
||||
*
|
||||
* request req;
|
||||
* req.push(command::subscribe, std::cbegin(channels), std::cedn(channels));
|
||||
* \endcode
|
||||
*
|
||||
* \param cmd The Redis command
|
||||
* \param begin Iterator to the begin of the range.
|
||||
* \param end Iterator to the end of the range.
|
||||
*/
|
||||
template <class ForwardIterator>
|
||||
void push_range(Command cmd, ForwardIterator begin, ForwardIterator end)
|
||||
{
|
||||
using value_type = typename std::iterator_traits<ForwardIterator>::value_type;
|
||||
|
||||
auto constexpr size = detail::value_type_size<value_type>::size;
|
||||
auto const distance = std::distance(begin, end);
|
||||
detail::add_header(*request_, 1 + size * distance);
|
||||
detail::add_bulk(*request_, to_string(cmd));
|
||||
|
||||
for (; begin != end; ++begin)
|
||||
detail::add_bulk(*request_, *begin);
|
||||
}
|
||||
};
|
||||
|
||||
} // resp3
|
||||
} // aedis
|
||||
@@ -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
|
||||
@@ -52,28 +53,26 @@ enum class command {
|
||||
/// https://redis.io/commands/unsubscribe
|
||||
unsubscribe,
|
||||
/// Unknown/invalid command.
|
||||
unknown
|
||||
invalid,
|
||||
};
|
||||
|
||||
/** \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/impl/node.ipp>
|
||||
#include <aedis/redis/experimental/impl/client.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.0.1], [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])
|
||||
@@ -18,5 +18,10 @@ AC_CHECK_HEADER_STDBOOL
|
||||
AC_TYPE_UINT64_T
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
|
||||
AX_CXX_COMPILE_STDCXX(14, , mandatory)
|
||||
AX_CXX_COMPILE_STDCXX(20, , optional)
|
||||
|
||||
AM_CONDITIONAL(HAVE_CXX20,[test x$HAVE_CXX20 == x1])
|
||||
|
||||
AC_CONFIG_FILES([Makefile doc/Doxyfile])
|
||||
AC_OUTPUT
|
||||
|
||||
@@ -32,13 +32,13 @@ DOXYFILE_ENCODING = UTF-8
|
||||
# title of most generated pages and in a few other places.
|
||||
# The default value is: My Project.
|
||||
|
||||
PROJECT_NAME = "Aedis"
|
||||
PROJECT_NAME = "@PACKAGE_NAME@"
|
||||
|
||||
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = "0.0.1"
|
||||
PROJECT_NUMBER = "@PACKAGE_VERSION@"
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
@@ -179,7 +179,7 @@ STRIP_FROM_PATH =
|
||||
# specify the list of include paths that are normally passed to the compiler
|
||||
# using the -I flag.
|
||||
|
||||
STRIP_FROM_INC_PATH =
|
||||
STRIP_FROM_INC_PATH = .
|
||||
|
||||
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
|
||||
# less readable) file names. This can be useful is your file systems doesn't
|
||||
@@ -850,52 +850,7 @@ INPUT_ENCODING = UTF-8
|
||||
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
|
||||
# *.vhdl, *.ucf, *.qsf and *.ice.
|
||||
|
||||
FILE_PATTERNS = *.c \
|
||||
*.cc \
|
||||
*.cxx \
|
||||
*.cpp \
|
||||
*.c++ \
|
||||
*.java \
|
||||
*.ii \
|
||||
*.ixx \
|
||||
*.ipp \
|
||||
*.i++ \
|
||||
*.inl \
|
||||
*.ddl \
|
||||
*.odl \
|
||||
*.h \
|
||||
*.hh \
|
||||
*.hxx \
|
||||
*.hpp \
|
||||
*.h++ \
|
||||
*.cs \
|
||||
*.d \
|
||||
*.php \
|
||||
*.php4 \
|
||||
*.php5 \
|
||||
*.phtml \
|
||||
*.inc \
|
||||
*.m \
|
||||
*.markdown \
|
||||
*.md \
|
||||
*.mm \
|
||||
*.dox \
|
||||
*.doc \
|
||||
*.txt \
|
||||
*.py \
|
||||
*.pyw \
|
||||
*.f90 \
|
||||
*.f95 \
|
||||
*.f03 \
|
||||
*.f08 \
|
||||
*.f \
|
||||
*.for \
|
||||
*.tcl \
|
||||
*.vhd \
|
||||
*.vhdl \
|
||||
*.ucf \
|
||||
*.qsf \
|
||||
*.ice
|
||||
FILE_PATTERNS = *.hpp *.cpp
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
@@ -910,7 +865,7 @@ RECURSIVE = YES
|
||||
# Note that relative paths are relative to the directory from which doxygen is
|
||||
# run.
|
||||
|
||||
EXCLUDE = include/aedis/impl include/aedis/resp3/impl include/aedis/resp3/detail
|
||||
EXCLUDE =
|
||||
|
||||
# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
|
||||
# directories that are symbolic links (a Unix file system feature) are excluded
|
||||
@@ -943,7 +898,7 @@ EXCLUDE_SYMBOLS = std
|
||||
# that contain example code fragments that are included (see the \include
|
||||
# command).
|
||||
|
||||
EXAMPLE_PATH = examples
|
||||
EXAMPLE_PATH =
|
||||
|
||||
# If the value of the EXAMPLE_PATH tag contains directories, you can use the
|
||||
# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
|
||||
@@ -24,9 +24,7 @@ div.contents {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
/*
|
||||
code
|
||||
{
|
||||
background-color:#EFD25E;
|
||||
background-color:#f0e9ce;
|
||||
}
|
||||
*/
|
||||
|
||||
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,100 +1,89 @@
|
||||
/* 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 <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.hpp"
|
||||
#include "user_session.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
namespace net = boost::asio;
|
||||
using aedis::resp3::node;
|
||||
using aedis::resp3::type;
|
||||
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;
|
||||
|
||||
// TODO: Delete sessions that have expired.
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
public:
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::vector<std::weak_ptr<user_session_base>> sessions_;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
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)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Message so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
case command::unknown: // Push
|
||||
{
|
||||
for (auto& weak: sessions_) {
|
||||
if (auto session = weak.lock()) {
|
||||
session->deliver(resps_.at(3).data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
default: { /* Ignore */ }
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
void on_read(command cmd, std::size_t)
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::subscribe, "channel");
|
||||
break;
|
||||
|
||||
case command::incr:
|
||||
std::cout << "Messages so far: " << resp_.front().value << std::endl;
|
||||
break;
|
||||
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push(std::size_t)
|
||||
{
|
||||
for (auto& session: sessions_)
|
||||
session->deliver(resp_.at(3).value);
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
auto add(std::shared_ptr<user_session_base> session)
|
||||
{ sessions_.push_back(session); }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_t<response_type> adapter_;
|
||||
std::shared_ptr<client_type> db_;
|
||||
std::vector<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<net::ip::tcp::acceptor> acc,
|
||||
std::shared_ptr<client_type> db,
|
||||
std::shared_ptr<receiver> recv)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> listener()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
db->send(command::subscribe, "channel");
|
||||
|
||||
auto on_user_msg = [db](std::string const& msg)
|
||||
{
|
||||
db->send(command::publish, "channel", msg);
|
||||
@@ -102,10 +91,10 @@ net::awaitable<void> listener()
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto socket = co_await acc->async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
recv->add(session);
|
||||
session->start(on_user_msg);
|
||||
recv->add(session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +102,21 @@ int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
|
||||
auto db = std::make_shared<client_type>(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;});
|
||||
|
||||
auto endpoint = net::ip::tcp::endpoint{net::ip::tcp::v4(), 55555};
|
||||
auto acc = std::make_shared<net::ip::tcp::acceptor>(ioc.get_executor(), endpoint);
|
||||
co_spawn(ioc, listener(acc, db, recv), net::detached);
|
||||
|
||||
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
|
||||
signals.async_wait([&] (auto, int) { ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
|
||||
63
examples/custom_adapter.cpp
Normal file
63
examples/custom_adapter.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
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 tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::string request, buffer;
|
||||
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::ping, "Some message.");
|
||||
sr.push(command::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
auto adapter = [](node<boost::string_view> const& nd, boost::system::error_code&)
|
||||
{
|
||||
std::cout << nd << std::endl;
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,106 +1,116 @@
|
||||
/* 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 <queue>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/user_session.hpp"
|
||||
#include "lib/net_utils.hpp"
|
||||
#include "user_session.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
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 aedis::resp3::node;
|
||||
using aedis::resp3::adapt;
|
||||
using aedis::resp3::experimental::client;
|
||||
using aedis::resp3::type;
|
||||
|
||||
class receiver : public std::enable_shared_from_this<receiver> {
|
||||
private:
|
||||
std::vector<node> resps_;
|
||||
std::queue<std::weak_ptr<user_session_base>> sessions_;
|
||||
using client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = std::vector<node<std::string>>;
|
||||
|
||||
class receiver {
|
||||
public:
|
||||
void on_message(std::error_code ec, command cmd)
|
||||
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)
|
||||
{
|
||||
if (ec) {
|
||||
std::cerr << "Error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
adapter_(nd, ec);
|
||||
}
|
||||
|
||||
void on_read(command cmd, std::size_t n)
|
||||
{
|
||||
std::cout << "on_read: " << cmd << " " << n << std::endl;
|
||||
|
||||
switch (cmd) {
|
||||
case command::ping:
|
||||
{
|
||||
if (auto session = sessions_.front().lock()) {
|
||||
session->deliver(resps_.front().data);
|
||||
} else {
|
||||
std::cout << "Session expired." << std::endl;
|
||||
}
|
||||
|
||||
if (resp_.front().value != "PONG") {
|
||||
sessions_.front()->deliver(resp_.front().value);
|
||||
sessions_.pop();
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case command::incr:
|
||||
{
|
||||
std::cout << "Echos so far: " << resps_.front().data << std::endl;
|
||||
} break;
|
||||
default: { /* Ignore */; }
|
||||
std::cout << "Echos so far: " << resp_.front().value << std::endl;
|
||||
break;
|
||||
|
||||
default: /* Ignore */;
|
||||
}
|
||||
|
||||
resps_.clear();
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
auto get_extended_adapter()
|
||||
{
|
||||
return [adapter = adapt(resps_)](command, type t, std::size_t aggregate_size, std::size_t depth, char const* data, std::size_t size, std::error_code& ec) mutable
|
||||
{ return adapter(t, aggregate_size, depth, data, size, ec); };
|
||||
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_t<response_type> adapter_;
|
||||
std::shared_ptr<client_type> db_;
|
||||
std::queue<std::shared_ptr<user_session_base>> sessions_;
|
||||
};
|
||||
|
||||
net::awaitable<void> connection_manager(std::shared_ptr<client> db)
|
||||
net::awaitable<void>
|
||||
run_with_reconnect(std::shared_ptr<client_type> db)
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
co_await db->engage(std::move(socket));
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
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()
|
||||
net::awaitable<void>
|
||||
listener(
|
||||
std::shared_ptr<net::ip::tcp::acceptor> acc,
|
||||
std::shared_ptr<client_type> db,
|
||||
std::shared_ptr<receiver> recv)
|
||||
{
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
net::ip::tcp::acceptor acceptor(ex, {net::ip::tcp::v4(), 55555});
|
||||
|
||||
auto recv = std::make_shared<receiver>();
|
||||
auto on_db_msg = [recv](std::error_code ec, command cmd)
|
||||
{ recv->on_message(ec, cmd); };
|
||||
|
||||
auto db = std::make_shared<client>(ex);
|
||||
db->set_extended_adapter(recv->get_extended_adapter());
|
||||
db->set_msg_callback(on_db_msg);
|
||||
net::co_spawn(ex, connection_manager(db), net::detached);
|
||||
|
||||
for (;;) {
|
||||
auto socket = co_await acceptor.async_accept(net::use_awaitable);
|
||||
auto socket = co_await acc->async_accept(net::use_awaitable);
|
||||
auto session = std::make_shared<user_session>(std::move(socket));
|
||||
|
||||
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);
|
||||
@@ -110,10 +120,20 @@ net::awaitable<void> listener()
|
||||
int main()
|
||||
{
|
||||
try {
|
||||
net::io_context ioc{1};
|
||||
net::signal_set signals(ioc, SIGINT, SIGTERM);
|
||||
signals.async_wait([&](auto, auto){ ioc.stop(); });
|
||||
co_spawn(ioc, listener(), net::detached);
|
||||
net::io_context ioc;
|
||||
|
||||
auto db = std::make_shared<client_type>(ioc.get_executor());
|
||||
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);
|
||||
co_spawn(ioc, listener(acc, db, recv), net::detached);
|
||||
|
||||
net::signal_set signals(ioc.get_executor(), SIGINT, SIGTERM);
|
||||
signals.async_wait([&] (auto, int) { ioc.stop(); });
|
||||
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
|
||||
@@ -1,88 +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 <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::map<std::string, std::string> map
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::hset, "key", std::cbegin(map), std::cend(map));
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::hgetall, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// The expected responses
|
||||
int hset;
|
||||
std::vector<std::string> hgetall1;
|
||||
std::map<std::string, std::string> hgetall2;
|
||||
std::unordered_map<std::string, std::string> hgetall3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hset));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(hgetall3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "hset: " << hset;
|
||||
std::cout << "\nhgetall (as vector): ";
|
||||
for (auto const& e: hgetall1) std::cout << e << ", ";
|
||||
std::cout << "\nhgetall (as map): ";
|
||||
for (auto const& e: hgetall2) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\nhgetall (as unordered_map): ";
|
||||
for (auto const& e: hgetall3) std::cout << e.first << " ==> " << e.second << "; ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,68 +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 <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect(); // See lib/net_utils.hpp
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::ping);
|
||||
sr.push(command::incr, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Responses we are interested in.
|
||||
int incr;
|
||||
std::string ping;
|
||||
|
||||
// Reads the responses to ping and incr, ignore the others.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(ping));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(incr));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Print the responses.
|
||||
std::cout
|
||||
<< "ping: " << ping << "\n"
|
||||
<< "incr: " << incr << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
76
examples/intro_high_level.cpp
Normal file
76
examples/intro_high_level.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* 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 <iostream>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
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 client_type = client<net::ip::tcp::socket, command>;
|
||||
using response_type = node<std::string>;
|
||||
|
||||
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, std::size_t)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::ping, "O rato roeu a roupa do rei de Roma");
|
||||
db_->send(command::incr, "intro-counter");
|
||||
db_->send(command::set, "intro-key", "Três pratos de trigo para três tigres");
|
||||
db_->send(command::get, "intro-key");
|
||||
db_->send(command::quit);
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << resp_.value << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push(std::size_t n) { }
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_t<response_type> adapter_;
|
||||
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,22 +1,23 @@
|
||||
/* 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>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::adapter::adapt;
|
||||
using net::dynamic_buffer;
|
||||
using net::ip::tcp;
|
||||
|
||||
@@ -27,24 +28,25 @@ int main()
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
connect(socket, res);
|
||||
net::connect(socket, res);
|
||||
|
||||
// Creates and sends the 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();
|
||||
|
||||
// Will store the response to ping.
|
||||
// Responses
|
||||
std::string resp;
|
||||
|
||||
// Reads the responses to all commands in the request.
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer)); // hello (ignored)
|
||||
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 << "Ping: " << resp << std::endl;
|
||||
|
||||
@@ -1,93 +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 <chrono>
|
||||
#include <optional>
|
||||
|
||||
#include <aedis/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> key_expiration()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the first request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", "Some payload", "EX", "2");
|
||||
sr.push(command::get, "key");
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Will hold the response to get.
|
||||
std::optional<std::string> get;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
std::cout
|
||||
<< "Before expiration: " << get.has_value() << ", "
|
||||
<< *get << std::endl;
|
||||
|
||||
// Waits some seconds for the key to expire.
|
||||
timer tm{socket.get_executor(), std::chrono::seconds{3}};
|
||||
co_await tm.async_wait();
|
||||
|
||||
// Creates a request to get after expiration.
|
||||
get.reset(); request.clear();
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to the second request.
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(get));
|
||||
|
||||
// Reading without an optional will result in an error.
|
||||
std::string str;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer),
|
||||
adapt(str), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
// Quit
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer));
|
||||
|
||||
std::cout << "After expiration (optional): " << get.has_value() << "\n";
|
||||
std::cout << "After expiration (non-optional): " << ec.message() << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, key_expiration(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,51 +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 <aedis/config.hpp>
|
||||
|
||||
using tcp_socket = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::socket>;
|
||||
using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
using timer = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::steady_timer>;
|
||||
|
||||
aedis::net::awaitable<tcp_socket>
|
||||
connect(
|
||||
std::string host = "127.0.0.1",
|
||||
std::string port = "6379")
|
||||
{
|
||||
auto ex = co_await aedis::net::this_coro::executor;
|
||||
tcp_resolver resolver{ex};
|
||||
auto const res = co_await resolver.async_resolve(host, port);
|
||||
tcp_socket socket{ex};
|
||||
co_await aedis::net::async_connect(socket, res);
|
||||
co_return std::move(socket);
|
||||
}
|
||||
|
||||
//net::awaitable<void>
|
||||
//client::connection_manager()
|
||||
//{
|
||||
// using namespace aedis::net::experimental::awaitable_operators;
|
||||
// using tcp_resolver = aedis::net::use_awaitable_t<>::as_default_on_t<aedis::net::ip::tcp::resolver>;
|
||||
//
|
||||
// for (;;) {
|
||||
// tcp_resolver resolver{socket_.get_executor()};
|
||||
// auto const res = co_await resolver.async_resolve("127.0.0.1", "6379");
|
||||
// co_await net::async_connect(socket_, res);
|
||||
//
|
||||
// co_await say_hello();
|
||||
//
|
||||
// timer_.expires_at(std::chrono::steady_clock::time_point::max());
|
||||
// co_await (reader() && writer());
|
||||
//
|
||||
// socket_.close();
|
||||
// timer_.cancel();
|
||||
//
|
||||
// timer_.expires_after(std::chrono::seconds{1});
|
||||
// boost::system::error_code ec;
|
||||
// co_await timer_.async_wait(net::redirect_error(net::use_awaitable, ec));
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -1,89 +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 <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> ping()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
// Creates and sends the request.
|
||||
auto vec = {1, 2, 3, 4, 5, 6};
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(vec), std::cend(vec));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int rpush;
|
||||
std::vector<std::string> svec;
|
||||
std::list<std::string> slist;
|
||||
std::deque<std::string> sdeq;
|
||||
std::vector<int> ivec;
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(rpush)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(svec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(slist));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(sdeq));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), adapt(ivec));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "rpush: " << rpush;
|
||||
std::cout << "\nlrange (as vector): ";
|
||||
for (auto e: svec) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as list): ";
|
||||
for (auto e: slist) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as deque): ";
|
||||
for (auto e: sdeq) std::cout << e << " ";
|
||||
std::cout << "\nlrange (as vector<int>): ";
|
||||
for (auto e: ivec) std::cout << e << " ";
|
||||
std::cout << std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, ping(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot 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 <string_view>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using resp3::type;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// An adapter that prints the data it receives in the screen.
|
||||
struct myadapter {
|
||||
void operator()(
|
||||
type t,
|
||||
std::size_t aggregate_size,
|
||||
std::size_t depth,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code&)
|
||||
{
|
||||
std::cout
|
||||
<< "Type: " << t << "\n"
|
||||
<< "Aggregate_size: " << aggregate_size << "\n"
|
||||
<< "Depth: " << depth << "\n"
|
||||
<< "Data: " << std::string_view(data, size) << "\n"
|
||||
<< "----------------------" << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
net::awaitable<void> adapter_example()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::rpush, "key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "key", 0, -1);
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Reads the responses.
|
||||
std::string rbuffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer), myadapter{}); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(rbuffer)); // quit
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, adapter_example(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,109 +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 <charconv>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::make_serializer;
|
||||
using aedis::redis::command;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
// Define the to_string and from_string functions for your own data
|
||||
// types.
|
||||
|
||||
// The struct we would like to store in redis using our own
|
||||
// serialization.
|
||||
struct mydata {
|
||||
int a;
|
||||
int b;
|
||||
};
|
||||
|
||||
// Serializes to Tab Separated Value (TSV).
|
||||
std::string to_string(mydata const& obj)
|
||||
{
|
||||
return std::to_string(obj.a) + '\t' + std::to_string(obj.b);
|
||||
}
|
||||
|
||||
// Deserializes TSV.
|
||||
void
|
||||
from_string(
|
||||
mydata& obj,
|
||||
char const* data,
|
||||
std::size_t size,
|
||||
std::error_code& ec)
|
||||
{
|
||||
auto const* end = data + size;
|
||||
auto const* pos = std::find(data, end, '\t');
|
||||
assert(pos != end); // Or use your own error code.
|
||||
|
||||
auto const res1 = std::from_chars(data, pos, obj.a);
|
||||
if (res1.ec != std::errc()) {
|
||||
ec = std::make_error_code(res1.ec);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const res2 = std::from_chars(pos + 1, end, obj.b);
|
||||
if (res2.ec != std::errc()) {
|
||||
ec = std::make_error_code(res2.ec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> serialization()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
mydata obj{21, 22};
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "key", obj);
|
||||
sr.push(command::get, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// The response.
|
||||
mydata get;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // set
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(get));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Print the responses.
|
||||
std::cout << "get: a = " << get.a << ", b = " << get.b << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, serialization(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
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,85 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot 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 <set>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> containers()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
|
||||
std::set<std::string> set
|
||||
{"one", "two", "three", "four"};
|
||||
|
||||
// Creates and sends the request.
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push_range(command::sadd, "key", std::cbegin(set), std::cend(set));
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::smembers, "key");
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
int sadd;
|
||||
std::vector<std::string> smembers1;
|
||||
std::set<std::string> smembers2;
|
||||
std::unordered_set<std::string> smembers3;
|
||||
|
||||
// Reads the responses.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(sadd));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers1));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers2));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(smembers3));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
|
||||
// Prints the responses.
|
||||
std::cout << "sadd: " << sadd;
|
||||
std::cout << "\nsmembers (as vector): ";
|
||||
for (auto const& e: smembers1) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as set): ";
|
||||
for (auto const& e: smembers2) std::cout << e << " ";
|
||||
std::cout << "\nsmembers (as unordered_set): ";
|
||||
for (auto const& e: smembers3) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, containers(), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
@@ -1,86 +1,65 @@
|
||||
/* 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>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
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>;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
reading server side messages indefinitely.
|
||||
|
||||
Notice we store the id of the connection (attributed by the redis
|
||||
server) to be able to identify it (in the logs for example).
|
||||
|
||||
After starting the example you can test it by sending messages with
|
||||
redis-cli like this
|
||||
|
||||
$ redis-cli -3
|
||||
127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
(integer) 3
|
||||
127.0.0.1:6379>
|
||||
|
||||
The messages will then appear on the terminal you are running the
|
||||
example.
|
||||
*/
|
||||
net::awaitable<void> subscriber()
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, "3");
|
||||
sr.push(command::subscribe, "channel1", "channel2");
|
||||
co_await async_write(socket, buffer(request));
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::vector<node> resp;
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::subscribe, "channel1", "channel2");
|
||||
co_await net::async_write(socket, buffer(request));
|
||||
|
||||
// Reads the response to the hello command.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer));
|
||||
// Ignores the response to hello.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer));
|
||||
|
||||
// Saves the id of this connection.
|
||||
auto const id = resp.at(8).data;
|
||||
|
||||
// Loops to receive server pushes.
|
||||
for (;;) {
|
||||
resp.clear();
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(resp));
|
||||
|
||||
std::cout
|
||||
<< "Subscriber " << id << ":\n"
|
||||
<< resp << std::endl;
|
||||
}
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
for (std::vector<node<std::string>> 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();
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
co_spawn(ioc, subscriber(), net::detached);
|
||||
ioc.run();
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
93
examples/subscriber_high_level.cpp
Normal file
93
examples/subscriber_high_level.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/* 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 <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using aedis::resp3::node;
|
||||
using aedis::sentinel::command;
|
||||
using aedis::generic::client;
|
||||
using aedis::adapter::adapt;
|
||||
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>;
|
||||
|
||||
/* In this example we send a subscription to a channel and start
|
||||
* reading server side messages indefinitely.
|
||||
*
|
||||
* After starting the example you can test it by sending messages with
|
||||
* redis-cli like this
|
||||
*
|
||||
* $ redis-cli -3
|
||||
* 127.0.0.1:6379> PUBLISH channel1 some-message
|
||||
* (integer) 3
|
||||
* 127.0.0.1:6379>
|
||||
*
|
||||
* The messages will then appear on the terminal you are running the
|
||||
* example.
|
||||
*/
|
||||
|
||||
class 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, std::size_t)
|
||||
{
|
||||
switch (cmd) {
|
||||
case command::hello:
|
||||
db_->send(command::subscribe, "channel1", "channel2");
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
void on_write(std::size_t n)
|
||||
{
|
||||
std::cout << "Number of bytes written: " << n << std::endl;
|
||||
}
|
||||
|
||||
void on_push(std::size_t)
|
||||
{
|
||||
std::cout
|
||||
<< "Event: " << resp_.at(1).value << "\n"
|
||||
<< "Channel: " << resp_.at(2).value << "\n"
|
||||
<< "Message: " << resp_.at(3).value << "\n"
|
||||
<< std::endl;
|
||||
|
||||
resp_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
response_type resp_;
|
||||
adapter_type adapter_;
|
||||
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,85 +1,73 @@
|
||||
/* 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>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "lib/net_utils.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using aedis::adapter::adapt;
|
||||
using aedis::generic::make_serializer;
|
||||
using net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<net::ip::tcp::socket>;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using net::async_write;
|
||||
using net::buffer;
|
||||
using net::dynamic_buffer;
|
||||
|
||||
net::awaitable<void> transaction()
|
||||
net::awaitable<void> example()
|
||||
{
|
||||
try {
|
||||
auto socket = co_await connect();
|
||||
auto ex = co_await net::this_coro::executor;
|
||||
|
||||
auto list = {"one", "two", "three"};
|
||||
tcp::resolver resv{ex};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp_socket socket{ex};
|
||||
co_await socket.async_connect(*std::begin(res));
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::multi); // Starts a transaction
|
||||
sr.push(command::ping, "Some message");
|
||||
sr.push(command::incr, "incr1-key");
|
||||
sr.push_range(command::rpush, "list-key", std::cbegin(list), std::cend(list));
|
||||
sr.push(command::lrange, "list-key", 0, -1);
|
||||
sr.push(command::incr, "incr2-key");
|
||||
sr.push(command::exec); // Ends the transaction.
|
||||
sr.push(command::quit);
|
||||
co_await async_write(socket, buffer(request));
|
||||
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::quit);
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
// Expected responses.
|
||||
std::tuple<std::string, int, int, std::vector<std::string>, int> execs;
|
||||
std::tuple<std::string, boost::optional<std::string>> response;
|
||||
|
||||
// Reads the response.
|
||||
std::string buffer;
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // hello
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // flushall
|
||||
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)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // rpush
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // lrange
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // incr
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer), adapt(execs));
|
||||
co_await resp3::async_read(socket, dynamic_buffer(buffer)); // quit
|
||||
std::string 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
|
||||
|
||||
// Prints the response to the transaction.
|
||||
std::cout << "ping: " << std::get<0>(execs) << "\n";
|
||||
std::cout << "incr1: " << std::get<1>(execs) << "\n";
|
||||
std::cout << "rpush: " << std::get<2>(execs) << "\n";
|
||||
std::cout << "lrange: ";
|
||||
for (auto const& e: std::get<3>(execs)) std::cout << e << " ";
|
||||
std::cout << "\n";
|
||||
std::cout << "incr2: " << std::get<4>(execs) << "\n";
|
||||
std::cout
|
||||
<< "Ping: " << std::get<0>(response) << "\n"
|
||||
<< "Get (has_value): " << std::get<1>(response).has_value()
|
||||
<< std::endl;
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (std::get<1>(response).has_value())
|
||||
std::cout << "Get (value): " << std::get<1>(response).value() << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc;
|
||||
co_spawn(ioc, transaction(), net::detached);
|
||||
ioc.run();
|
||||
try {
|
||||
net::io_context ioc;
|
||||
net::co_spawn(ioc, example(), net::detached);
|
||||
ioc.run();
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +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_USER_SESSION_HPP
|
||||
#define AEDIS_USER_SESSION_HPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <aedis/config.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/co_spawn.hpp>
|
||||
#include <boost/asio/redirect_error.hpp>
|
||||
|
||||
// An example user session.
|
||||
|
||||
@@ -26,7 +29,7 @@ class user_session:
|
||||
public user_session_base,
|
||||
public std::enable_shared_from_this<user_session> {
|
||||
public:
|
||||
user_session(net::ip::tcp::socket socket)
|
||||
user_session(boost::asio::ip::tcp::socket socket)
|
||||
: socket_(std::move(socket))
|
||||
, timer_(socket_.get_executor())
|
||||
{ timer_.expires_at(std::chrono::steady_clock::time_point::max()); }
|
||||
@@ -35,11 +38,11 @@ public:
|
||||
{
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this(), on_msg]{ return self->reader(on_msg); },
|
||||
net::detached);
|
||||
boost::asio::detached);
|
||||
|
||||
co_spawn(socket_.get_executor(),
|
||||
[self = shared_from_this()]{ return self->writer(); },
|
||||
net::detached);
|
||||
boost::asio::detached);
|
||||
}
|
||||
|
||||
void deliver(std::string const& msg)
|
||||
@@ -49,12 +52,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
net::awaitable<void>
|
||||
boost::asio::awaitable<void>
|
||||
reader(std::function<void(std::string const&)> on_msg)
|
||||
{
|
||||
try {
|
||||
for (std::string msg;;) {
|
||||
auto const n = co_await net::async_read_until(socket_, net::dynamic_buffer(msg, 1024), "\n", net::use_awaitable);
|
||||
auto const n = co_await boost::asio::async_read_until(socket_, boost::asio::dynamic_buffer(msg, 1024), "\n", boost::asio::use_awaitable);
|
||||
on_msg(msg);
|
||||
msg.erase(0, n);
|
||||
}
|
||||
@@ -63,15 +66,15 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> writer()
|
||||
boost::asio::awaitable<void> writer()
|
||||
{
|
||||
try {
|
||||
while (socket_.is_open()) {
|
||||
if (write_msgs_.empty()) {
|
||||
boost::system::error_code ec;
|
||||
co_await timer_.async_wait(redirect_error(net::use_awaitable, ec));
|
||||
co_await timer_.async_wait(boost::asio::redirect_error(boost::asio::use_awaitable, ec));
|
||||
} else {
|
||||
co_await net::async_write(socket_, net::buffer(write_msgs_.front()), net::use_awaitable);
|
||||
co_await boost::asio::async_write(socket_, boost::asio::buffer(write_msgs_.front()), boost::asio::use_awaitable);
|
||||
write_msgs_.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -86,9 +89,10 @@ private:
|
||||
timer_.cancel();
|
||||
}
|
||||
|
||||
net::ip::tcp::socket socket_;
|
||||
net::steady_timer timer_;
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
boost::asio::steady_timer timer_;
|
||||
std::deque<std::string> write_msgs_;
|
||||
};
|
||||
|
||||
} // aedis
|
||||
#endif // AEDIS_USER_SESSION_HPP
|
||||
1005
m4/ax_cxx_compile_stdcxx.m4
Normal file
1005
m4/ax_cxx_compile_stdcxx.m4
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,43 @@
|
||||
/* 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 <stdlib.h>
|
||||
|
||||
template <class T>
|
||||
void check_equal(T const& a, T const& b, std::string const& msg = "")
|
||||
void expect_eq(T const& a, T const& b, std::string const& msg = "")
|
||||
{
|
||||
if (a == b)
|
||||
if (a == b) {
|
||||
std::cout << "Success: " << msg << std::endl;
|
||||
else
|
||||
} else {
|
||||
std::cout << "Error: " << msg << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void expect_error(boost::system::error_code a, T expected = {})
|
||||
{
|
||||
if (a == expected) {
|
||||
if (a)
|
||||
std::cout << "Success: " << a.message() << " (" << a.category().name() << ")" << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << a.message() << " (" << a.category().name() << ")" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void check_empty(T const& t)
|
||||
{
|
||||
if (t.empty()) {
|
||||
//std::cout << "Success: " << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: Not empty" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
717
tests/low_level.cpp
Normal file
717
tests/low_level.cpp
Normal file
@@ -0,0 +1,717 @@
|
||||
/* 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 <iostream>
|
||||
#include <optional>
|
||||
|
||||
#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/beast/_experimental/test/stream.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
#include "check.hpp"
|
||||
#include "config.h"
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
|
||||
using test_stream = boost::beast::test::stream;
|
||||
using aedis::adapter::adapt;
|
||||
using node_type = aedis::resp3::node<std::string>;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
template <class Result>
|
||||
struct expect {
|
||||
std::string in;
|
||||
Result expected;
|
||||
std::string name;
|
||||
boost::system::error_code ec;
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
void test_sync(net::any_io_executor ex, expect<Result> e)
|
||||
{
|
||||
std::string rbuffer;
|
||||
test_stream ts {ex};
|
||||
ts.append(e.in);
|
||||
Result result;
|
||||
boost::system::error_code ec;
|
||||
resp3::read(ts, net::dynamic_buffer(rbuffer), adapt(result), ec);
|
||||
expect_error(ec, e.ec);
|
||||
if (e.ec)
|
||||
return;
|
||||
check_empty(rbuffer);
|
||||
expect_eq(result, e.expected, e.name);
|
||||
}
|
||||
|
||||
template <class Result>
|
||||
class async_test: public std::enable_shared_from_this<async_test<Result>> {
|
||||
private:
|
||||
std::string rbuffer_;
|
||||
test_stream ts_;
|
||||
expect<Result> data_;
|
||||
Result result_;
|
||||
|
||||
public:
|
||||
async_test(net::any_io_executor ex, expect<Result> e)
|
||||
: ts_{ex}
|
||||
, data_{e}
|
||||
{
|
||||
ts_.append(e.in);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
auto self = this->shared_from_this();
|
||||
auto f = [self](auto ec, auto n)
|
||||
{
|
||||
expect_error(ec, self->data_.ec);
|
||||
if (self->data_.ec)
|
||||
return;
|
||||
check_empty(self->rbuffer_);
|
||||
expect_eq(self->result_, self->data_.expected, self->data_.name);
|
||||
};
|
||||
|
||||
resp3::async_read(
|
||||
ts_,
|
||||
net::dynamic_buffer(rbuffer_),
|
||||
adapt(result_),
|
||||
f);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Result>
|
||||
void test_async(net::any_io_executor ex, expect<Result> e)
|
||||
{
|
||||
std::make_shared<async_test<Result>>(ex, e)->run();
|
||||
}
|
||||
|
||||
void test_number(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<int> ok;
|
||||
ok = 11;
|
||||
|
||||
// Success
|
||||
auto const in01 = expect<node_type>{":3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"3"}}, "number.node (positive)"};
|
||||
auto const in02 = expect<node_type>{":-3\r\n", node_type{resp3::type::number, 1UL, 0UL, {"-3"}}, "number.node (negative)"};
|
||||
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_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();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_bool(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<bool> ok;
|
||||
ok = true;
|
||||
|
||||
// Success.
|
||||
auto const in08 = expect<node_type>{"#f\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"f"}}, "bool.node (false)"};
|
||||
auto const in09 = expect<node_type>{"#t\r\n", node_type{resp3::type::boolean, 1UL, 0UL, {"t"}}, "bool.node (true)"};
|
||||
auto const in10 = expect<bool>{"#t\r\n", bool{true}, "bool.bool (true)"};
|
||||
auto const in11 = expect<bool>{"#f\r\n", bool{false}, "bool.bool (true)"};
|
||||
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_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();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in10);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in10);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_streamed_string(net::io_context& ioc)
|
||||
{
|
||||
std::string const wire = "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {aedis::resp3::type::streamed_string_part, 1, 1, "Hell"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "o wor"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, "d"}
|
||||
, {aedis::resp3::type::streamed_string_part, 1, 1, ""}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::streamed_string_part, 1UL, 1UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "streamed_string.node"};
|
||||
auto const in03 = expect<std::string>{wire, std::string{"Hello word"}, "streamed_string.string"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"$?\r\n;0\r\n", e1b, "streamed_string.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_push(net::io_context& ioc)
|
||||
{
|
||||
std::string const wire = ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::push, 4UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "pubsub"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "message"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "some-channel"}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "some message"}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b { {resp3::type::push, 0UL, 0UL, {}} };
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "push.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{">0\r\n", e1b, "push.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_map(net::io_context& ioc)
|
||||
{
|
||||
using map_type = std::map<std::string, std::string>;
|
||||
using mmap_type = std::multimap<std::string, std::string>;
|
||||
using umap_type = std::unordered_map<std::string, std::string>;
|
||||
using mumap_type = std::unordered_multimap<std::string, std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_map_type = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
using tuple_type = std::tuple<std::string, std::string, std::string, std::string, std::string, std::string, std::string, std::string>;
|
||||
|
||||
std::string const wire = "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n";
|
||||
|
||||
std::vector<node_type> expected_1a
|
||||
{ {resp3::type::map, 4UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value1"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key2"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value2"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"key3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"value3"}}
|
||||
};
|
||||
|
||||
map_type expected_1b
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
umap_type e1g
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
mmap_type e1k
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
mumap_type e1l
|
||||
{ {"key1", "value1"}
|
||||
, {"key2", "value2"}
|
||||
, {"key3", "value3"}
|
||||
, {"key3", "value3"}
|
||||
};
|
||||
|
||||
std::vector<std::string> expected_1c
|
||||
{ "key1", "value1"
|
||||
, "key2", "value2"
|
||||
, "key3", "value3"
|
||||
, "key3", "value3"
|
||||
};
|
||||
|
||||
op_map_type expected_1d;
|
||||
expected_1d = expected_1b;
|
||||
|
||||
op_vec_type expected_1e;
|
||||
expected_1e = expected_1c;
|
||||
|
||||
tuple_type e1f
|
||||
{ std::string{"key1"}, std::string{"value1"}
|
||||
, std::string{"key2"}, std::string{"value2"}
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
, std::string{"key3"}, std::string{"value3"}
|
||||
};
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected_1a, "map.node"};
|
||||
auto const in01 = expect<map_type>{"%0\r\n", map_type{}, "map.map.empty"};
|
||||
auto const in02 = expect<map_type>{wire, expected_1b, "map.map"};
|
||||
auto const in03 = expect<mmap_type>{wire, e1k, "map.multimap"};
|
||||
auto const in04 = expect<umap_type>{wire, e1g, "map.unordered_map"};
|
||||
auto const in05 = expect<mumap_type>{wire, e1l, "map.unordered_multimap"};
|
||||
auto const in06 = expect<vec_type>{wire, expected_1c, "map.vector"};
|
||||
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_code(aedis::adapter::error::expects_simple_type)};
|
||||
auto const in11 = expect<tuple_type>{wire, e1f, "map.tuple"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in11);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in11);
|
||||
}
|
||||
|
||||
void test_attribute(net::io_context& ioc)
|
||||
{
|
||||
char const* wire = "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::attribute, 1UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, "key-popularity"}
|
||||
, {resp3::type::map, 2UL, 1UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 2UL, "a"}
|
||||
, {resp3::type::doublean, 1UL, 2UL, "0.1923"}
|
||||
, {resp3::type::blob_string, 1UL, 2UL, "b"}
|
||||
, {resp3::type::doublean, 1UL, 2UL, "0.0012"}
|
||||
};
|
||||
|
||||
std::vector<node_type> e1b;
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "attribute.node"};
|
||||
auto const in02 = expect<std::vector<node_type>>{"|0\r\n", e1b, "attribute.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_array(net::io_context& ioc)
|
||||
{
|
||||
char const* wire = "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n";
|
||||
|
||||
std::vector<node_type> e1a
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
};
|
||||
|
||||
std::vector<int> const e1b{11, 22, 3};
|
||||
std::vector<std::string> const e1c{"11", "22", "3"};
|
||||
std::vector<std::string> const e1d{};
|
||||
std::vector<node_type> const e1e{{resp3::type::array, 0UL, 0UL, {}}};
|
||||
std::array<int, 3> const e1f{11, 22, 3};
|
||||
std::list<int> const e1g{11, 22, 3};
|
||||
std::deque<int> const e1h{11, 22, 3};
|
||||
|
||||
auto const in01 = expect<std::vector<node_type>>{wire, e1a, "array.node"};
|
||||
auto const in02 = expect<std::vector<int>>{wire, e1b, "array.int"};
|
||||
auto const in03 = expect<std::vector<node_type>>{"*0\r\n", e1e, "array.node.empty"};
|
||||
auto const in04 = expect<std::vector<std::string>>{"*0\r\n", e1d, "array.string.empty"};
|
||||
auto const in05 = expect<std::vector<std::string>>{wire, e1c, "array.string"};
|
||||
auto const in06 = expect<std::array<int, 3>>{wire, e1f, "array.array"};
|
||||
auto const in07 = expect<std::list<int>>{wire, e1g, "array.list"};
|
||||
auto const in08 = expect<std::deque<int>>{wire, e1h, "array.deque"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
}
|
||||
|
||||
void test_set(net::io_context& ioc)
|
||||
{
|
||||
using set_type = std::set<std::string>;
|
||||
using mset_type = std::multiset<std::string>;
|
||||
using uset_type = std::unordered_set<std::string>;
|
||||
using muset_type = std::unordered_multiset<std::string>;
|
||||
using vec_type = std::vector<std::string>;
|
||||
using op_vec_type = boost::optional<std::vector<std::string>>;
|
||||
|
||||
std::string const wire = "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n";
|
||||
std::vector<node_type> const expected1a
|
||||
{ {resp3::type::set, 6UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
};
|
||||
|
||||
mset_type const e1f{"apple", "one", "orange", "orange", "three", "two"};
|
||||
uset_type const e1c{"apple", "one", "orange", "three", "two"};
|
||||
muset_type const e1g{"apple", "one", "orange", "orange", "three", "two"};
|
||||
vec_type const e1d = {"orange", "apple", "one", "two", "three", "orange"};
|
||||
op_vec_type expected_1e;
|
||||
expected_1e = e1d;
|
||||
|
||||
auto const in00 = expect<std::vector<node_type>>{wire, expected1a, "set.node"};
|
||||
auto const in01 = expect<std::vector<node_type>>{"~0\r\n", std::vector<node_type>{ {resp3::type::set, 0UL, 0UL, {}} }, "set.node (empty)"};
|
||||
auto const in02 = expect<set_type>{wire, set_type{"apple", "one", "orange", "three", "two"}, "set.set"};
|
||||
auto const in03 = expect<mset_type>{wire, e1f, "set.multiset"};
|
||||
auto const in04 = expect<vec_type>{wire, e1d, "set.vector "};
|
||||
auto const in05 = expect<op_vec_type>{wire, expected_1e, "set.vector "};
|
||||
auto const in06 = expect<uset_type>{wire, e1c, "set.unordered_set"};
|
||||
auto const in07 = expect<muset_type>{wire, e1g, "set.unordered_multiset"};
|
||||
auto const in08 = expect<std::tuple<uset_type>>{"*1\r\n" + wire, std::tuple<uset_type>{e1c}, "set.tuple"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
}
|
||||
|
||||
void test_simple_error(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"-Error\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, "simple_error.node"};
|
||||
auto const in02 = expect<node_type>{"-\r\n", node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, "simple_error.node.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_blob_string(net::io_context& ioc)
|
||||
{
|
||||
std::string str(100000, 'a');
|
||||
str[1000] = '\r';
|
||||
str[1001] = '\n';
|
||||
|
||||
std::string wire;
|
||||
wire += '$';
|
||||
wire += std::to_string(str.size());
|
||||
wire += "\r\n";
|
||||
wire += str;
|
||||
wire += "\r\n";
|
||||
|
||||
auto const in01 = expect<node_type>{"$2\r\nhh\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}, "blob_string.node"};
|
||||
auto const in02 = expect<node_type>{"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}, "blob_string.node (with separator)"};
|
||||
auto const in03 = expect<node_type>{"$0\r\n\r\n", node_type{resp3::type::blob_string, 1UL, 0UL, {}}, "blob_string.node.empty"};
|
||||
auto const in04 = expect<node_type>{wire, node_type{resp3::type::blob_string, 1UL, 0UL, {str}}, "blob_string.node (long string)"};
|
||||
|
||||
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_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)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"!21\r\nSYNTAX invalid syntax\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, "blob_error"};
|
||||
auto const in02 = expect<node_type>{"!0\r\n\r\n", node_type{resp3::type::blob_error, 1UL, 0UL, {}}, "blob_error.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_verbatim_string(net::io_context& ioc)
|
||||
{
|
||||
auto const in01 = expect<node_type>{"=15\r\ntxt:Some string\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}, "verbatim_string"};
|
||||
auto const in02 = expect<node_type>{"=0\r\n\r\n", node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}, "verbatim_string.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
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_code(aedis::resp3::error::empty_field)};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
}
|
||||
|
||||
void test_simple_string(net::io_context& ioc)
|
||||
{
|
||||
boost::optional<std::string> ok1, ok2;
|
||||
ok1 = "OK";
|
||||
ok2 = "";
|
||||
|
||||
auto in00 = expect<std::string>{"+OK\r\n", std::string{"OK"}, "simple_string.string"};
|
||||
auto in01 = expect<std::string>{"+\r\n", std::string{""}, "simple_string.string.empty"};
|
||||
auto in02 = expect<boost::optional<std::string>>{"+OK\r\n", boost::optional<std::string>{"OK"}, "simple_string.optional"};
|
||||
auto in03 = expect<boost::optional<std::string>>{"+\r\n", boost::optional<std::string>{""}, "simple_string.optional.empty"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in00);
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
|
||||
test_async(ex, in00);
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
}
|
||||
|
||||
void test_resp3(net::io_context& ioc)
|
||||
{
|
||||
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();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
}
|
||||
|
||||
void test_null(net::io_context& ioc)
|
||||
{
|
||||
using op_type_01 = boost::optional<bool>;
|
||||
using op_type_02 = boost::optional<int>;
|
||||
using op_type_03 = boost::optional<std::string>;
|
||||
using op_type_04 = boost::optional<std::vector<std::string>>;
|
||||
using op_type_05 = boost::optional<std::list<std::string>>;
|
||||
using op_type_06 = boost::optional<std::map<std::string, std::string>>;
|
||||
using op_type_07 = boost::optional<std::unordered_map<std::string, std::string>>;
|
||||
using op_type_08 = boost::optional<std::set<std::string>>;
|
||||
using op_type_09 = boost::optional<std::unordered_set<std::string>>;
|
||||
|
||||
auto const in01 = expect<op_type_01>{"_\r\n", op_type_01{}, "null.optional.bool"};
|
||||
auto const in02 = expect<op_type_02>{"_\r\n", op_type_02{}, "null.optional.int"};
|
||||
auto const in03 = expect<op_type_03>{"_\r\n", op_type_03{}, "null.optional.string"};
|
||||
auto const in04 = expect<op_type_04>{"_\r\n", op_type_04{}, "null.optional.vector"};
|
||||
auto const in05 = expect<op_type_05>{"_\r\n", op_type_05{}, "null.optional.list"};
|
||||
auto const in06 = expect<op_type_06>{"_\r\n", op_type_06{}, "null.optional.map"};
|
||||
auto const in07 = expect<op_type_07>{"_\r\n", op_type_07{}, "null.optional.unordered_map"};
|
||||
auto const in08 = expect<op_type_08>{"_\r\n", op_type_08{}, "null.optional.set"};
|
||||
auto const in09 = expect<op_type_09>{"_\r\n", op_type_09{}, "null.optional.unordered_set"};
|
||||
|
||||
auto ex = ioc.get_executor();
|
||||
|
||||
test_sync(ex, in01);
|
||||
test_sync(ex, in02);
|
||||
test_sync(ex, in03);
|
||||
test_sync(ex, in04);
|
||||
test_sync(ex, in05);
|
||||
test_sync(ex, in06);
|
||||
test_sync(ex, in07);
|
||||
test_sync(ex, in08);
|
||||
test_sync(ex, in09);
|
||||
|
||||
test_async(ex, in01);
|
||||
test_async(ex, in02);
|
||||
test_async(ex, in03);
|
||||
test_async(ex, in04);
|
||||
test_async(ex, in05);
|
||||
test_async(ex, in06);
|
||||
test_async(ex, in07);
|
||||
test_async(ex, in08);
|
||||
test_async(ex, in09);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
net::io_context ioc {1};
|
||||
|
||||
// Simple types.
|
||||
test_simple_string(ioc);
|
||||
test_simple_error(ioc);
|
||||
test_blob_string(ioc);
|
||||
test_blob_error(ioc);
|
||||
test_number(ioc);
|
||||
test_double(ioc);
|
||||
test_bool(ioc);
|
||||
test_null(ioc);
|
||||
test_big_number(ioc);
|
||||
test_verbatim_string(ioc);
|
||||
|
||||
// Aggregates.
|
||||
test_array(ioc);
|
||||
test_set(ioc);
|
||||
test_map(ioc);
|
||||
test_push(ioc);
|
||||
test_streamed_string(ioc);
|
||||
|
||||
// RESP3
|
||||
test_resp3(ioc);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,648 +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 <aedis/src.hpp>
|
||||
#include <aedis/aedis.hpp>
|
||||
#include "test_stream.hpp"
|
||||
#include "check.hpp"
|
||||
|
||||
// Consider using Beast test_stream and instantiate the test socket
|
||||
// only once.
|
||||
|
||||
namespace net = aedis::net;
|
||||
using tcp = net::ip::tcp;
|
||||
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
|
||||
|
||||
namespace this_coro = net::this_coro;
|
||||
|
||||
using namespace aedis;
|
||||
using namespace aedis::resp3;
|
||||
|
||||
std::vector<node> gresp;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
void simple_string_sync()
|
||||
{
|
||||
test_tcp_socket ts {"+OK\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
|
||||
check_equal(result, expected, "simple_string (node-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::string expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (string-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (optional-string-sync)");
|
||||
}
|
||||
}
|
||||
|
||||
void simple_string_sync_empty()
|
||||
{
|
||||
test_tcp_socket ts {"+\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "simple_string (empty-node-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-string-sync)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
resp3::read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-optional-string-sync)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> simple_string_async()
|
||||
{
|
||||
test_tcp_socket ts {"+OK\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {"OK"}};
|
||||
check_equal(result, expected, "simple_string (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::string expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {"OK"};
|
||||
check_equal(result, expected, "simple_string (optional-string-async)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> simple_string_async_empty()
|
||||
{
|
||||
test_tcp_socket ts {"+\r\n"};
|
||||
std::string rbuffer;
|
||||
auto dbuffer = net::dynamic_buffer(rbuffer);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
node expected {resp3::type::simple_string, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "simple_string (empty-node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuffer, adapt(result));
|
||||
std::optional<std::string> expected {""};
|
||||
check_equal(result, expected, "simple_string (empty-optional-string-async)");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test a large simple string. For example
|
||||
// std::string str(10000, 'a');
|
||||
// std::string cmd;
|
||||
// cmd += '+';
|
||||
// cmd += str;
|
||||
// cmd += "\r\n";
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_simple_error_async()
|
||||
{
|
||||
std::string buf;
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-Error\r\n"};
|
||||
node result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
node expected {resp3::type::simple_error, 1UL, 0UL, {"Error"}};
|
||||
check_equal(result, expected, "simple_error (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-Error\r\n"};
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
std::string expected {"Error"};
|
||||
check_equal(result, expected, "simple_error (string-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"-\r\n"};
|
||||
std::string result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
std::string expected {""};
|
||||
check_equal(result, expected, "simple_error (empty-string-async)");
|
||||
}
|
||||
|
||||
// TODO: Test with optional.
|
||||
// TODO: Test with a very long string?
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_number()
|
||||
{
|
||||
std::string buf;
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":-3\r\n"};
|
||||
node result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
node expected {resp3::type::number, 1UL, 0UL, {"-3"}};
|
||||
check_equal(result, expected, "number (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":-3\r\n"};
|
||||
long long result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
check_equal(result, -3LL, "number (signed-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {":3\r\n"};
|
||||
std::size_t result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
check_equal(result, 3UL, "number (unsigned-async)");
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> array_async()
|
||||
{
|
||||
test_tcp_socket ts {"*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n"};
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
std::vector<node> result;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::array, 3UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"11"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"22"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"3"}}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "array (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<int> result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
|
||||
std::vector<int> expected {11, 22, 3};
|
||||
check_equal(result, expected, "array (int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"*0\r\n"};
|
||||
|
||||
std::vector<int> result;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(result));
|
||||
|
||||
std::vector<int> expected;
|
||||
check_equal(result, expected, "array (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void> test_blob_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"$2\r\nhh\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"hh"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string (with separator)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_string (size 0)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_floating_point()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {",1.23\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"1.23"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "double");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {",inf\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"inf"}} };
|
||||
check_equal(resp, expected, "double (inf)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {",-inf\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::doublean, 1UL, 0UL, {"-inf"}} };
|
||||
check_equal(resp, expected, "double (-inf)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
net::awaitable<void> test_boolean()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"#f\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::boolean, 1UL, 0UL, {"f"}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "bool (false)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"#t\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::boolean, 1UL, 0UL, {"t"}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
check_equal(resp, expected, "bool (true)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_blob_error()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"!21\r\nSYNTAX invalid syntax\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_error (message)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"!0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_error, 1UL, 0UL, {}} };
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "blob_error (empty message)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_verbatim_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"=15\r\ntxt:Some string\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "verbatim_string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"=0\r\n\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::verbatim_string, 1UL, 0UL, {}} };
|
||||
check_equal(gresp, expected, "verbatim_string (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_set2()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"~5\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::set, 5UL, 0UL, {}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"orange"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"apple"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"one"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"two"}}
|
||||
, {resp3::type::simple_string, 1UL, 1UL, {"three"}}
|
||||
};
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "test set (1)");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"~0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::set, 0UL, 0UL, {}}
|
||||
};
|
||||
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "test set (2)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_flat_map_async()
|
||||
{
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"%3\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n"};
|
||||
std::map<std::string, std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
|
||||
std::map<std::string, std::string> expected
|
||||
{ {"server", "redis"}
|
||||
, {"version", "6.0.9"}
|
||||
, {"proto", "3"}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "map (flat-map-async)");
|
||||
}
|
||||
|
||||
{
|
||||
test_tcp_socket ts {"%0\r\n"};
|
||||
std::map<std::string, std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::map<std::string, std::string> expected;
|
||||
check_equal(result, expected, "map (flat-empty-map-async)");
|
||||
}
|
||||
|
||||
{
|
||||
// TODO: Why do we get a crash when %3. It should produce an
|
||||
// error instead.
|
||||
test_tcp_socket ts {"%2\r\n$6\r\nserver\r\n$2\r\n10\r\n$7\r\nversion\r\n$2\r\n30\r\n"};
|
||||
std::map<std::string, int> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
|
||||
std::map<std::string, int> expected
|
||||
{ {"server", 10}
|
||||
, {"version", 30}
|
||||
};
|
||||
|
||||
check_equal(result, expected, "map (flat-map-string-int-async)");
|
||||
}
|
||||
|
||||
// TODO: Test optional map.
|
||||
// TODO: Test serializaition with different key and value types.
|
||||
}
|
||||
|
||||
net::awaitable<void> test_map()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"%7\r\n$6\r\nserver\r\n$5\r\nredis\r\n$7\r\nversion\r\n$5\r\n6.0.9\r\n$5\r\nproto\r\n:3\r\n$2\r\nid\r\n:203\r\n$4\r\nmode\r\n$10\r\nstandalone\r\n$4\r\nrole\r\n$6\r\nmaster\r\n$7\r\nmodules\r\n*0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::map, 7UL, 0UL, {}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"server"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"redis"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"version"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"6.0.9"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"proto"}}
|
||||
, {resp3::type::number, 1UL, 1UL, {"3"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"id"}}
|
||||
, {resp3::type::number, 1UL, 1UL, {"203"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"mode"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"standalone"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"role"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"master"}}
|
||||
, {resp3::type::blob_string, 1UL, 1UL, {"modules"}}
|
||||
, {resp3::type::array, 0UL, 1UL, {}}
|
||||
};
|
||||
check_equal(gresp, expected, "test map");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"%0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::map, 0UL, 0UL, {}} };
|
||||
check_equal(gresp, expected, "test map (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
net::awaitable<void> test_streamed_string()
|
||||
{
|
||||
using namespace aedis;
|
||||
std::string buf;
|
||||
{
|
||||
std::string cmd {"$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::streamed_string_part, 1UL, 0UL, {"Hello world"}} };
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "streamed string");
|
||||
}
|
||||
|
||||
{
|
||||
std::string cmd {"$?\r\n;0\r\n"};
|
||||
test_tcp_socket ts {cmd};
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(ts, net::dynamic_buffer(buf), adapt(resp));
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::streamed_string_part, 1UL, 0UL, {}} };
|
||||
check_equal(resp, expected, "streamed string (empty)");
|
||||
}
|
||||
}
|
||||
|
||||
//net::awaitable<void> offline()
|
||||
//{
|
||||
// std::string buf;
|
||||
// //{
|
||||
// // std::string cmd {"|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {"key-popularity", "a", "0.1923", "b", "0.0012"}, "attribute");
|
||||
// //}
|
||||
//
|
||||
// //{
|
||||
// // std::string cmd {">4\r\n+pubsub\r\n+message\r\n+foo\r\n+bar\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {"pubsub", "message", "foo", "bar"}, "push type");
|
||||
// //}
|
||||
//
|
||||
// //{
|
||||
// // std::string cmd {">0\r\n"};
|
||||
// // test_tcp_socket ts {cmd};
|
||||
// // resp3::flat_radapter res;
|
||||
// // co_await async_read(ts, buf, res);
|
||||
// // check_equal(res.result, {}, "push type (empty)");
|
||||
// //}
|
||||
//}
|
||||
|
||||
net::awaitable<void> optional_async()
|
||||
{
|
||||
test_tcp_socket ts {"_\r\n"};
|
||||
std::string buf;
|
||||
auto dbuf = net::dynamic_buffer(buf);
|
||||
|
||||
{
|
||||
node result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
node expected {resp3::type::null, 1UL, 0UL, {""}};
|
||||
check_equal(result, expected, "optional (node-async)");
|
||||
}
|
||||
|
||||
{
|
||||
int result;
|
||||
boost::system::error_code ec;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result), net::redirect_error(net::use_awaitable, ec));
|
||||
|
||||
//auto const expected = make_error_code(adapter::error::null);
|
||||
//std::cout << expected.message() << std::endl;
|
||||
// TODO: Convert to std::error_code.
|
||||
//check_equal(ec, expected, "optional (int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<int> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::optional<int> expected;
|
||||
check_equal(result, expected, "optional (optional-int-async)");
|
||||
}
|
||||
|
||||
{
|
||||
std::optional<std::string> result;
|
||||
co_await resp3::async_read(ts, dbuf, adapt(result));
|
||||
std::optional<std::string> expected;
|
||||
check_equal(result, expected, "optional (optional-int-async)");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
simple_string_sync();
|
||||
simple_string_sync_empty();
|
||||
|
||||
net::io_context ioc {1};
|
||||
|
||||
co_spawn(ioc, simple_string_async(), net::detached);
|
||||
co_spawn(ioc, simple_string_async_empty(), net::detached);
|
||||
co_spawn(ioc, test_simple_error_async(), net::detached);
|
||||
co_spawn(ioc, test_number(), net::detached);
|
||||
co_spawn(ioc, test_map(), net::detached);
|
||||
co_spawn(ioc, test_flat_map_async(), net::detached);
|
||||
co_spawn(ioc, optional_async(), net::detached);
|
||||
|
||||
co_spawn(ioc, array_async(), net::detached);
|
||||
co_spawn(ioc, test_blob_string(), net::detached);
|
||||
co_spawn(ioc, test_floating_point(), net::detached);
|
||||
co_spawn(ioc, test_boolean(), net::detached);
|
||||
co_spawn(ioc, test_blob_error(), net::detached);
|
||||
co_spawn(ioc, test_verbatim_string(), net::detached);
|
||||
co_spawn(ioc, test_set2(), net::detached);
|
||||
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
581
tests/online.cpp
581
tests/online.cpp
@@ -1,581 +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 <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
#include "test_stream.hpp"
|
||||
#include "check.hpp"
|
||||
|
||||
namespace net = aedis::net;
|
||||
using tcp = net::ip::tcp;
|
||||
using tcp_socket = net::use_awaitable_t<>::as_default_on_t<tcp::socket>;
|
||||
using test_tcp_socket = net::use_awaitable_t<>::as_default_on_t<aedis::test_stream<aedis::net::system_executor>>;
|
||||
|
||||
namespace this_coro = net::this_coro;
|
||||
|
||||
using namespace aedis;
|
||||
using namespace aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
|
||||
std::vector<node> gresp;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
net::awaitable<void>
|
||||
test_general(net::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
auto ex = co_await 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", std::cbegin(list_), std::cend(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", std::cbegin(m1), std::cend(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", std::cbegin(v), std::cend(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> 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> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, n} };
|
||||
|
||||
check_equal(resp, expected, "rpush (value)");
|
||||
}
|
||||
|
||||
{ // llen
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::number, 1UL, 0UL, {"6"}} };
|
||||
check_equal(resp, expected, "llen");
|
||||
}
|
||||
|
||||
{ // lrange
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> 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"}}
|
||||
};
|
||||
|
||||
check_equal(resp, expected, "lrange ");
|
||||
}
|
||||
|
||||
{ // ltrim
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
check_equal(resp, expected, "ltrim");
|
||||
}
|
||||
|
||||
{ // lpop
|
||||
std::vector<node> resp;
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
|
||||
|
||||
check_equal(resp, expected, "lpop");
|
||||
}
|
||||
|
||||
//{ // lpop
|
||||
// std::vector<node> resp;
|
||||
// co_await resp3::async_read(socket, net::dynamic_buffer(buffer), adapt(resp), net::use_awaitable);
|
||||
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"4"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"5"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "lpop");
|
||||
//}
|
||||
|
||||
//{ // lrange
|
||||
// static int c = 0;
|
||||
|
||||
// if (c == 0) {
|
||||
// std::vector<node> 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"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "lrange ");
|
||||
// } else {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// check_equal(resp, expected, "lrange (inside transaction)");
|
||||
// }
|
||||
//
|
||||
// ++c;
|
||||
//}
|
||||
|
||||
//for (;;) {
|
||||
// switch (cmd) {
|
||||
// case command::multi:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "multi");
|
||||
// } break;
|
||||
// case command::ping:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"QUEUED"}} };
|
||||
|
||||
// check_equal(resp, expected, "ping");
|
||||
// } break;
|
||||
// case command::set:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "set");
|
||||
// } break;
|
||||
// case command::quit:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "quit");
|
||||
// } break;
|
||||
// case command::flushall:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
|
||||
// check_equal(resp, expected, "flushall");
|
||||
// } break;
|
||||
// case command::append:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"4"}} };
|
||||
|
||||
// check_equal(resp, expected, "append");
|
||||
// } break;
|
||||
// case command::hset:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hset");
|
||||
// } break;
|
||||
// case command::del:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "del");
|
||||
// } break;
|
||||
// case command::incr:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "incr");
|
||||
// } break;
|
||||
// case command::publish:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "publish");
|
||||
// } break;
|
||||
// case command::hincrby:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"10"}} };
|
||||
|
||||
// check_equal(resp, expected, "hincrby");
|
||||
// } break;
|
||||
// case command::zadd:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "zadd");
|
||||
// } break;
|
||||
// case command::sadd:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"3"}} };
|
||||
|
||||
// check_equal(resp, expected, "sadd");
|
||||
// } break;
|
||||
// case command::hdel:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hdel");
|
||||
// } break;
|
||||
// case command::zremrangebyscore:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"1"}} };
|
||||
|
||||
// check_equal(resp, expected, "zremrangebyscore");
|
||||
// } break;
|
||||
// case command::get:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, test.set_} };
|
||||
|
||||
// check_equal(resp, expected, "get");
|
||||
// } break;
|
||||
// case command::hget:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, std::string{"value2"}} };
|
||||
|
||||
// check_equal(resp, expected, "hget");
|
||||
// } break;
|
||||
// case command::hvals:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::array, 2UL, 0UL, {}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value1"}}
|
||||
// , {resp3::type::array, 1UL, 1UL, {"value2"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrange:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "hvals");
|
||||
// } break;
|
||||
// case command::zrangebyscore:
|
||||
// {
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::array, 1UL, 0UL, {}}
|
||||
// , {resp3::type::blob_string, 1UL, 1UL, {"Marcelo"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "zrangebyscore");
|
||||
// } break;
|
||||
// case command::exec:
|
||||
// {
|
||||
// std::vector<node> 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"}}
|
||||
// };
|
||||
|
||||
// check_equal(resp, expected, "transaction");
|
||||
|
||||
// } break;
|
||||
// case command::hgetall:
|
||||
// {
|
||||
// std::vector<node> 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"}}
|
||||
// };
|
||||
// check_equal(resp, expected, "hgetall (value)");
|
||||
// } break;
|
||||
// case command::smembers:
|
||||
// {
|
||||
// std::vector<node> 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"}}
|
||||
// };
|
||||
// check_equal(resp, expected, "smembers (value)");
|
||||
// } break;
|
||||
// default: { std::cout << "Error: " << resp.front().data_type << " " << cmd << std::endl; }
|
||||
// }
|
||||
|
||||
// resp.clear();
|
||||
//}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
//net::awaitable<void>
|
||||
//test_list(net::ip::tcp::resolver::results_type const& results)
|
||||
//{
|
||||
// std::vector<int> list {1 ,2, 3, 4, 5, 6};
|
||||
//
|
||||
// resp3::serializer p;
|
||||
// p.push(command::hello, 3);
|
||||
// p.push(command::flushall);
|
||||
// p.push_range(command::rpush, "a", std::cbegin(list), std::cend(list));
|
||||
// p.push(command::lrange, "a", 0, -1);
|
||||
// p.push(command::lrange, "a", 2, -2);
|
||||
// p.push(command::ltrim, "a", 2, -2);
|
||||
// p.push(command::lpop, "a");
|
||||
// p.push(command::quit);
|
||||
//
|
||||
// auto ex = co_await this_coro::executor;
|
||||
// tcp_socket socket {ex};
|
||||
// co_await async_connect(socket, results);
|
||||
// co_await async_write(socket, net::buffer(p.payload));
|
||||
// std::string buf;
|
||||
//
|
||||
// { // hello
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// }
|
||||
//
|
||||
// { // flushall
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
// check_equal(gresp, expected, "flushall");
|
||||
// }
|
||||
//
|
||||
// { // rpush
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::number, 1UL, 0UL, {"6"}} };
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "rpush");
|
||||
// }
|
||||
//
|
||||
// { // lrange
|
||||
// resp3::flat_array_int_type buffer;
|
||||
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
|
||||
// co_await async_read(socket, buf, res);
|
||||
// check_equal(buffer, list, "lrange-1");
|
||||
// }
|
||||
//
|
||||
// { // lrange
|
||||
// resp3::flat_array_int_type buffer;
|
||||
// resp3::detail::basic_flat_array_adapter<int> res{&buffer};
|
||||
// co_await async_read(socket, buf, res);
|
||||
// check_equal(buffer, std::vector<int>{3, 4, 5}, "lrange-2");
|
||||
// }
|
||||
//
|
||||
// { // ltrim
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
//
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "ltrim");
|
||||
// }
|
||||
//
|
||||
// { // lpop. Why a blob string instead of a number?
|
||||
// gresp.clear();
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::blob_string, 1UL, 0UL, {"3"}} };
|
||||
//
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// check_equal(gresp, expected, "lpop");
|
||||
// }
|
||||
//
|
||||
// { // quit
|
||||
// gresp.clear();
|
||||
// co_await async_read(socket, buf, gresp);
|
||||
// std::vector<node> expected
|
||||
// { {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
// check_equal(gresp, expected, "ltrim");
|
||||
// }
|
||||
//}
|
||||
|
||||
std::string test_bulk1(10000, 'a');
|
||||
|
||||
net::awaitable<void>
|
||||
test_set(net::ip::tcp::resolver::results_type const& results)
|
||||
{
|
||||
using namespace aedis;
|
||||
|
||||
// Tests whether the parser can handle payloads that contain the separator.
|
||||
test_bulk1[30] = '\r';
|
||||
test_bulk1[31] = '\n';
|
||||
|
||||
std::string test_bulk2 = "aaaaa";
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
sr.push(command::hello, 3);
|
||||
sr.push(command::flushall);
|
||||
sr.push(command::set, "s", test_bulk1);
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::set, "s", test_bulk2);
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::set, "s", "");
|
||||
sr.push(command::get, "s");
|
||||
sr.push(command::quit);
|
||||
|
||||
auto ex = co_await this_coro::executor;
|
||||
tcp_socket socket {ex};
|
||||
co_await async_connect(socket, results);
|
||||
|
||||
co_await net::async_write(socket, net::buffer(request));
|
||||
|
||||
std::string buf;
|
||||
{ // hello, flushall
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "set1");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk1} };
|
||||
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get1");
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "ltrim");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, test_bulk2} };
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get2");
|
||||
}
|
||||
|
||||
{ // set
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "set3");
|
||||
}
|
||||
|
||||
{ // get
|
||||
gresp.clear();
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::blob_string, 1UL, 0UL, {}} };
|
||||
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
check_equal(gresp, expected, "get3");
|
||||
}
|
||||
|
||||
{ // quit
|
||||
gresp.clear();
|
||||
co_await resp3::async_read(socket, net::dynamic_buffer(buf), adapt(gresp));
|
||||
std::vector<node> expected
|
||||
{ {resp3::type::simple_string, 1UL, 0UL, {"OK"}} };
|
||||
check_equal(gresp, expected, "quit");
|
||||
}
|
||||
}
|
||||
|
||||
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_list(res), net::detached);
|
||||
co_spawn(ioc, test_set(res), net::detached);
|
||||
co_spawn(ioc, test_general(res), net::detached);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/* Copyright (c) 2019 Marcelo Zimbres Silva (mzimbres at gmail dot 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 <aedis/aedis.hpp>
|
||||
|
||||
namespace aedis {
|
||||
|
||||
struct initiate_async_receive {
|
||||
using executor_type = aedis::net::system_executor;
|
||||
|
||||
std::string::const_iterator pbegin;
|
||||
std::string::const_iterator pend;
|
||||
|
||||
executor_type get_executor() noexcept
|
||||
{ return aedis::net::system_executor(); }
|
||||
|
||||
template <
|
||||
class ReadHandler,
|
||||
class MutableBufferSequence>
|
||||
void operator()(
|
||||
ReadHandler&& handler,
|
||||
MutableBufferSequence const& buffers)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (std::size(buffers) == 0) {
|
||||
handler(ec, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
auto begin = boost::asio::buffer_sequence_begin(buffers);
|
||||
auto end = boost::asio::buffer_sequence_end(buffers);
|
||||
//std::cout << "Buffers size: " << std::size(buffers) << std::endl;
|
||||
|
||||
std::size_t transferred = 0;
|
||||
while (begin != end) {
|
||||
//std::cout << "Buffer size: " << std::ssize(*begin) << std::endl;
|
||||
auto const min = std::min(std::ssize(*begin), pend - pbegin);
|
||||
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
|
||||
std::advance(pbegin, min);
|
||||
transferred += min;
|
||||
++begin;
|
||||
}
|
||||
|
||||
handler(ec, transferred);
|
||||
}
|
||||
};
|
||||
|
||||
template <class Executor>
|
||||
struct test_stream {
|
||||
std::string const payload;
|
||||
|
||||
using executor_type = Executor;
|
||||
|
||||
template<
|
||||
class MutableBufferSequence,
|
||||
class ReadHandler =
|
||||
aedis::net::default_completion_token_t<executor_type>
|
||||
>
|
||||
auto async_read_some(
|
||||
MutableBufferSequence const& buffers,
|
||||
ReadHandler&& handler = net::default_completion_token_t<executor_type>{})
|
||||
{
|
||||
return aedis::net::async_initiate<ReadHandler,
|
||||
void (boost::system::error_code, std::size_t)>(
|
||||
initiate_async_receive
|
||||
{std::cbegin(payload), std::cend(payload)},
|
||||
handler, buffers);
|
||||
}
|
||||
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t read_some(
|
||||
MutableBufferSequence const& buffers,
|
||||
boost::system::error_code&)
|
||||
{
|
||||
if (std::size(buffers) == 0)
|
||||
return 0;
|
||||
|
||||
boost::system::error_code ec;
|
||||
auto pbegin = std::cbegin(payload);
|
||||
auto pend = std::cend(payload);
|
||||
|
||||
auto begin = boost::asio::buffer_sequence_begin(buffers);
|
||||
auto end = boost::asio::buffer_sequence_end(buffers);
|
||||
|
||||
std::size_t transferred = 0;
|
||||
while (begin != end) {
|
||||
auto const min = std::min(std::ssize(*begin), pend - pbegin);
|
||||
std::copy(pbegin, pbegin + min, static_cast<char*>(begin->data()));
|
||||
std::advance(pbegin, min);
|
||||
transferred += min;
|
||||
++begin;
|
||||
}
|
||||
|
||||
return transferred;
|
||||
}
|
||||
|
||||
executor_type get_executor() noexcept
|
||||
{ return aedis::net::system_executor(); }
|
||||
|
||||
template<class Executor1>
|
||||
struct rebind_executor {
|
||||
using other = test_stream<Executor1>;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
/* 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 <algorithm>
|
||||
#include <cctype>
|
||||
|
||||
#include <boost/asio/connect.hpp>
|
||||
|
||||
#include <aedis/aedis.hpp>
|
||||
#include <aedis/src.hpp>
|
||||
|
||||
namespace net = boost::asio;
|
||||
namespace resp3 = aedis::resp3;
|
||||
using aedis::redis::command;
|
||||
using aedis::redis::make_serializer;
|
||||
using resp3::adapt;
|
||||
using resp3::node;
|
||||
|
||||
namespace net = aedis::net;
|
||||
using aedis::redis::command;
|
||||
using aedis::generic::make_serializer;
|
||||
using aedis::resp3::node;
|
||||
using aedis::adapter::adapt;
|
||||
using net::ip::tcp;
|
||||
using net::write;
|
||||
using net::buffer;
|
||||
@@ -32,17 +33,17 @@ std::string toupper(std::string s)
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
get_cmd_names(std::vector<node> const& resp)
|
||||
get_cmd_names(std::vector<node<std::string>> const& resp)
|
||||
{
|
||||
if (std::empty(resp)) {
|
||||
if (resp.empty()) {
|
||||
std::cerr << "Response is empty." << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> ret;
|
||||
for (auto i = 0ULL; i < std::size(resp); ++i) {
|
||||
for (auto i = 0ULL; i < resp.size(); ++i) {
|
||||
if (resp.at(i).depth == 1)
|
||||
ret.push_back(resp.at(i + 1).data);
|
||||
ret.push_back(resp.at(i + 1).value);
|
||||
}
|
||||
|
||||
std::sort(std::begin(ret), std::end(ret));
|
||||
@@ -58,7 +59,7 @@ void print_cmds_enum(std::vector<std::string> const& cmds)
|
||||
<< " " << cmd << ",\n";
|
||||
}
|
||||
|
||||
std::cout << " unknown\n};\n";
|
||||
std::cout << " invalid\n};\n";
|
||||
}
|
||||
|
||||
void print_cmds_strs(std::vector<std::string> const& cmds)
|
||||
@@ -78,7 +79,7 @@ int main()
|
||||
tcp::resolver resv{ioc};
|
||||
auto const res = resv.resolve("127.0.0.1", "6379");
|
||||
tcp::socket socket{ioc};
|
||||
connect(socket, res);
|
||||
net::connect(socket, res);
|
||||
|
||||
std::string request;
|
||||
auto sr = make_serializer(request);
|
||||
@@ -87,7 +88,7 @@ int main()
|
||||
sr.push(command::quit);
|
||||
write(socket, buffer(request));
|
||||
|
||||
std::vector<node> resp;
|
||||
std::vector<node<std::string>> resp;
|
||||
|
||||
std::string buffer;
|
||||
resp3::read(socket, dynamic_buffer(buffer));
|
||||
|
||||
Reference in New Issue
Block a user