diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java
new file mode 100644
index 0000000..a6971bf
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarker.java
@@ -0,0 +1,42 @@
+package org.springframework.data.r2dbc.dialect;
+
+import io.r2dbc.spi.Statement;
+
+/**
+ * A bind marker represents a single bindable parameter within a query. Bind markers are dialect-specific and provide a
+ * {@link #getPlaceholder() placeholder} that is used in the actual query.
+ *
+ * @author Mark Paluch
+ * @see Statement#bind
+ * @see BindMarkers
+ * @see BindMarkersFactory
+ */
+public interface BindMarker {
+
+ /**
+ * Returns the database-specific placeholder for a given substitution.
+ *
+ * @return the database-specific placeholder for a given substitution.
+ */
+ String getPlaceholder();
+
+ /**
+ * Bind the given {@code value} to the {@link Statement} using the underlying binding strategy.
+ *
+ * @param statement the statement to bind the value to.
+ * @param value the actual value. Must not be {@literal null}. Use {@link #bindNull(Statement, Class)} for
+ * {@literal null} values.
+ * @see Statement#bind
+ */
+ void bindValue(Statement> statement, Object value);
+
+ /**
+ * Bind a {@literal null} value to the {@link Statement} using the underlying binding strategy.
+ *
+ * @param statement the statement to bind the value to.
+ * @param valueType value type, must not be {@literal null}.
+ * @see Statement#bindNull
+ */
+
+ void bindNull(Statement> statement, Class> valueType);
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkers.java b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkers.java
new file mode 100644
index 0000000..63bb95f
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkers.java
@@ -0,0 +1,36 @@
+package org.springframework.data.r2dbc.dialect;
+
+/**
+ * Bind markers represent placeholders in SQL queries for substitution for an actual parameter. Using bind markers
+ * allows creating safe queries so query strings are not required to contain escaped values but rather the driver
+ * encodes parameter in the appropriate representation.
+ *
+ * {@link BindMarkers} is stateful and can be only used for a single binding pass of one or more parameters. It
+ * maintains bind indexes/bind parameter names.
+ *
+ * @author Mark Paluch
+ * @see BindMarker
+ * @see BindMarkersFactory
+ * @see io.r2dbc.spi.Statement#bind
+ */
+@FunctionalInterface
+public interface BindMarkers {
+
+ /**
+ * Creates a new {@link BindMarker}.
+ *
+ * @return a new {@link BindMarker}.
+ */
+ BindMarker next();
+
+ /**
+ * Creates a new {@link BindMarker} that accepts a {@code nameHint}. Implementations are allowed to consider/ignore
+ * the name hint to create more expressive bind markers.
+ *
+ * @param nameHint an optional name hint.
+ * @return a new {@link BindMarker}.
+ */
+ default BindMarker next(String nameHint) {
+ return next();
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkersFactory.java b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkersFactory.java
new file mode 100644
index 0000000..4832225
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/BindMarkersFactory.java
@@ -0,0 +1,53 @@
+package org.springframework.data.r2dbc.dialect;
+
+import org.springframework.util.Assert;
+
+/**
+ * This class creates new {@link BindMarkers} instances to bind parameter for a specific {@link io.r2dbc.spi.Statement}.
+ *
+ * @author Mark Paluch
+ * @see BindMarkers
+ * @see io.r2dbc.spi.Statement
+ */
+@FunctionalInterface
+public interface BindMarkersFactory {
+
+ /**
+ * Create a new {@link BindMarkers} instance.
+ *
+ * @return a new {@link BindMarkers} instance.
+ */
+ BindMarkers create();
+
+ /**
+ * Create index-based {@link BindMarkers}.
+ *
+ * @param prefix bind parameter prefix.
+ * @param beginWith the first index to use.
+ * @return a {@link BindMarkersFactory} using {@code prefix} and {@code beginWith}.
+ */
+ static BindMarkersFactory indexed(String prefix, int beginWith) {
+
+ Assert.notNull(prefix, "Prefix must not be null!");
+ return () -> new IndexedBindMarkers(prefix, beginWith);
+ }
+
+ /**
+ * Create named {@link BindMarkers}. Named bind markers can support {@link BindMarkers#next(String) name hints}.
+ * Typically, named markers use name hints. If no namehint is given, named bind markers use a counter to generate
+ * unique bind markers.
+ *
+ * @param prefix bind parameter prefix.
+ * @param indexPrefix prefix for bind markers that were created by incrementing a counter to generate a unique bind
+ * marker.
+ * @param nameLimit maximal length of parameter names when using name hints.
+ * @return a {@link BindMarkersFactory} using {@code prefix} and {@code beginWith}.
+ */
+ static BindMarkersFactory named(String prefix, String indexPrefix, int nameLimit) {
+
+ Assert.notNull(prefix, "Prefix must not be null!");
+ Assert.notNull(indexPrefix, "Index prefix must not be null!");
+
+ return () -> new NamedBindMarkers(prefix, indexPrefix, nameLimit);
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkers.java b/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkers.java
new file mode 100644
index 0000000..d77c467
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkers.java
@@ -0,0 +1,87 @@
+package org.springframework.data.r2dbc.dialect;
+
+import io.r2dbc.spi.Statement;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+/**
+ * Index-based bind marker. This implementation creates indexed bind markers using a numeric index and an optional
+ * prefix for bind markers to be represented within the query string.
+ *
+ * @author Mark Paluch
+ */
+class IndexedBindMarkers implements BindMarkers {
+
+ private static final AtomicIntegerFieldUpdater COUNTER_INCREMENTER = AtomicIntegerFieldUpdater
+ .newUpdater(org.springframework.data.r2dbc.dialect.IndexedBindMarkers.class, "counter");
+
+ // access via COUNTER_INCREMENTER
+ @SuppressWarnings("unused") private volatile int counter;
+
+ private final String prefix;
+
+ /**
+ * Creates a new {@link IndexedBindMarker} instance given {@code prefix} and {@code beginWith}.
+ *
+ * @param prefix bind parameter prefix.
+ * @param beginWith the first index to use.
+ */
+ IndexedBindMarkers(String prefix, int beginWith) {
+ this.counter = beginWith;
+ this.prefix = prefix;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarkers#next()
+ */
+ @Override
+ public BindMarker next() {
+
+ int index = COUNTER_INCREMENTER.getAndIncrement(this);
+
+ return new IndexedBindMarker(prefix + "" + index, index);
+ }
+
+ /**
+ * A single indexed bind marker.
+ */
+ static class IndexedBindMarker implements BindMarker {
+
+ private final String placeholder;
+
+ private int index;
+
+ IndexedBindMarker(String placeholder, int index) {
+ this.placeholder = placeholder;
+ this.index = index;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#getPlaceholder()
+ */
+ @Override
+ public String getPlaceholder() {
+ return placeholder;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object)
+ */
+ @Override
+ public void bindValue(Statement> statement, Object value) {
+ statement.bind(this.index, value);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class)
+ */
+ @Override
+ public void bindNull(Statement> statement, Class> valueType) {
+ statement.bindNull(this.index, valueType);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java b/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java
new file mode 100644
index 0000000..cf42c77
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/NamedBindMarkers.java
@@ -0,0 +1,129 @@
+package org.springframework.data.r2dbc.dialect;
+
+import io.r2dbc.spi.Statement;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+
+import org.springframework.util.Assert;
+
+/**
+ * Name-based bind markers.
+ *
+ * @author Mark Paluch
+ */
+class NamedBindMarkers implements BindMarkers {
+
+ private static final AtomicIntegerFieldUpdater COUNTER_INCREMENTER = AtomicIntegerFieldUpdater
+ .newUpdater(NamedBindMarkers.class, "counter");
+
+ // access via COUNTER_INCREMENTER
+ @SuppressWarnings("unused") private volatile int counter;
+
+ private final String prefix;
+
+ private final String indexPrefix;
+
+ private final int nameLimit;
+
+ NamedBindMarkers(String prefix, String indexPrefix, int nameLimit) {
+
+ this.prefix = prefix;
+ this.indexPrefix = indexPrefix;
+ this.nameLimit = nameLimit;
+ }
+
+ @Override
+ public BindMarker next() {
+
+ String name = nextName();
+
+ return new NamedBindMarker(prefix + name, name);
+ }
+
+ @Override
+ public BindMarker next(String nameHint) {
+
+ Assert.notNull(nameHint, "Name hint must not be null");
+
+ String name = nextName();
+
+ String filteredNameHint = filter(nameHint);
+
+ if (!filteredNameHint.isEmpty()) {
+ name += "_" + filteredNameHint;
+ }
+
+ if (name.length() > nameLimit) {
+ name = name.substring(0, nameLimit);
+ }
+
+ return new NamedBindMarker(prefix + name, name);
+ }
+
+ private String nextName() {
+
+ int index = COUNTER_INCREMENTER.getAndIncrement(this);
+ return indexPrefix + index;
+ }
+
+ private static String filter(CharSequence input) {
+
+ StringBuilder builder = new StringBuilder();
+
+ for (int i = 0; i < input.length(); i++) {
+
+ char ch = input.charAt(i);
+
+ // ascii letter or digit
+ if (Character.isLetterOrDigit(ch) && ch < 127) {
+ builder.append(ch);
+ }
+
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * A single named bind marker.
+ */
+ static class NamedBindMarker implements BindMarker {
+
+ private final String placeholder;
+
+ private final String identifier;
+
+ NamedBindMarker(String placeholder, String identifier) {
+
+ this.placeholder = placeholder;
+ this.identifier = identifier;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#getPlaceholder()
+ */
+ @Override
+ public String getPlaceholder() {
+ return this.placeholder;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#bindValue(io.r2dbc.spi.Statement, java.lang.Object)
+ */
+ @Override
+ public void bindValue(Statement> statement, Object value) {
+ statement.bind(this.identifier, value);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.r2dbc.dialect.BindMarker#bindNull(io.r2dbc.spi.Statement, java.lang.Class)
+ */
+ @Override
+ public void bindNull(Statement> statement, Class> valueType) {
+ statement.bindNull(this.identifier, valueType);
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java b/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java
new file mode 100644
index 0000000..9643ac2
--- /dev/null
+++ b/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Dialects abstract the SQL dialect of the underlying database.
+ */
+@NonNullApi
+package org.springframework.data.r2dbc.dialect;
+
+import org.springframework.lang.NonNullApi;
diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java
new file mode 100644
index 0000000..338a152
--- /dev/null
+++ b/src/test/java/org/springframework/data/r2dbc/dialect/IndexedBindMarkersUnitTests.java
@@ -0,0 +1,68 @@
+package org.springframework.data.r2dbc.dialect;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import io.r2dbc.spi.Statement;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link IndexedBindMarkers}.
+ *
+ * @author Mark Paluch
+ */
+public class IndexedBindMarkersUnitTests {
+
+ @Test // gh-15
+ public void shouldCreateNewBindMarkers() {
+
+ BindMarkersFactory factory = BindMarkersFactory.indexed("$", 0);
+
+ BindMarkers bindMarkers1 = factory.create();
+ BindMarkers bindMarkers2 = factory.create();
+
+ assertThat(bindMarkers1.next().getPlaceholder()).isEqualTo("$0");
+ assertThat(bindMarkers2.next().getPlaceholder()).isEqualTo("$0");
+ }
+
+ @Test // gh-15
+ public void nextShouldIncrementBindMarker() {
+
+ BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create();
+
+ BindMarker marker1 = bindMarkers.next();
+ BindMarker marker2 = bindMarkers.next();
+
+ assertThat(marker1.getPlaceholder()).isEqualTo("$0");
+ assertThat(marker2.getPlaceholder()).isEqualTo("$1");
+ }
+
+ @Test // gh-15
+ public void bindValueShouldBindByIndex() {
+
+ Statement> statement = mock(Statement.class);
+
+ BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create();
+
+ bindMarkers.next().bindValue(statement, "foo");
+ bindMarkers.next().bindValue(statement, "bar");
+
+ verify(statement).bind(0, "foo");
+ verify(statement).bind(1, "bar");
+ }
+
+ @Test // gh-15
+ public void bindNullShouldBindByIndex() {
+
+ Statement> statement = mock(Statement.class);
+
+ BindMarkers bindMarkers = BindMarkersFactory.indexed("$", 0).create();
+
+ bindMarkers.next(); // ignore
+
+ bindMarkers.next().bindNull(statement, Integer.class);
+
+ verify(statement).bindNull(1, Integer.class);
+ }
+}
diff --git a/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java b/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java
new file mode 100644
index 0000000..7da8385
--- /dev/null
+++ b/src/test/java/org/springframework/data/r2dbc/dialect/NamedBindMarkersUnitTests.java
@@ -0,0 +1,90 @@
+package org.springframework.data.r2dbc.dialect;
+
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import io.r2dbc.spi.Statement;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link NamedBindMarkers}.
+ *
+ * @author Mark Paluch
+ */
+public class NamedBindMarkersUnitTests {
+
+ @Test // gh-15
+ public void shouldCreateNewBindMarkers() {
+
+ BindMarkersFactory factory = BindMarkersFactory.named("@", "p", 32);
+
+ BindMarkers bindMarkers1 = factory.create();
+ BindMarkers bindMarkers2 = factory.create();
+
+ assertThat(bindMarkers1.next().getPlaceholder()).isEqualTo("@p0");
+ assertThat(bindMarkers2.next().getPlaceholder()).isEqualTo("@p0");
+ }
+
+ @Test // gh-15
+ public void nextShouldIncrementBindMarker() {
+
+ BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
+
+ BindMarker marker1 = bindMarkers.next();
+ BindMarker marker2 = bindMarkers.next();
+
+ assertThat(marker1.getPlaceholder()).isEqualTo("@p0");
+ assertThat(marker2.getPlaceholder()).isEqualTo("@p1");
+ }
+
+ @Test // gh-15
+ public void nextShouldConsiderNameHint() {
+
+ BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
+
+ BindMarker marker1 = bindMarkers.next("foo.bar?");
+ BindMarker marker2 = bindMarkers.next();
+
+ assertThat(marker1.getPlaceholder()).isEqualTo("@p0_foobar");
+ assertThat(marker2.getPlaceholder()).isEqualTo("@p1");
+ }
+
+ @Test // gh-15
+ public void nextShouldConsiderNameLimit() {
+
+ BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 10).create();
+
+ BindMarker marker1 = bindMarkers.next("123456789");
+
+ assertThat(marker1.getPlaceholder()).isEqualTo("@p0_1234567");
+ }
+
+ @Test // gh-15
+ public void bindValueShouldBindByName() {
+
+ Statement> statement = mock(Statement.class);
+
+ BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
+
+ bindMarkers.next().bindValue(statement, "foo");
+ bindMarkers.next().bindValue(statement, "bar");
+
+ verify(statement).bind("p0", "foo");
+ verify(statement).bind("p1", "bar");
+ }
+
+ @Test // gh-15
+ public void bindNullShouldBindByName() {
+
+ Statement> statement = mock(Statement.class);
+
+ BindMarkers bindMarkers = BindMarkersFactory.named("@", "p", 32).create();
+
+ bindMarkers.next(); // ignore
+
+ bindMarkers.next().bindNull(statement, Integer.class);
+
+ verify(statement).bindNull("p1", Integer.class);
+ }
+}